Client Area

Rust Web Applications on DomainIndia VPS: Actix-web and Axum

ByDomain India Team·DomainIndia Engineering
5 min readPublished 24 Apr 2026Updated 23 Jun 2026160 views

In this article

  • 1Why Rust for web apps
  • 2Actix-web vs Axum
  • 3Step 1 — Install Rust (on your laptop)
  • 4Step 2 — Create a minimal Axum API
  • 5Step 3 — Cross-compile for Linux

Rust Web Applications on DomainIndia VPS: Actix-web and Axum

TL;DR
Rust produces small, fast, memory-safe binaries — ideal for high-throughput APIs on a DomainIndia VPS. This guide covers picking Actix-web vs Axum, compiling for Linux, running under systemd, and fronting with nginx + SSL.

Why Rust for web apps

  • Performance — often 2–5× throughput of Node.js, 10× of Python for CPU-bound work
  • Memory — a typical Rust API uses 20–80 MB RAM (a Spring Boot equivalent: 300–800 MB)
  • Safety — no null pointer dereferences, no data races (compile-time guarantees)
  • Ecosystem (2026) — mature web frameworks, async runtime (tokio) is stable

Trade-offs:

  • Steep learning curve — borrow checker takes weeks to get comfortable with
  • Longer compile times than Go (~10× for a typical API)
  • Smaller talent pool compared to PHP/Node/Python

Good fit: performance-critical APIs, system tools, data processing pipelines. Questionable fit: CRUD apps under 100 req/sec (Node or Laravel are faster to ship).

Actix-web vs Axum

FrameworkAsync runtimeStyleBest for
Actix-webtokioActor model, middleware chainsHigh-throughput APIs, raw performance
AxumtokioTower-based, type-safe handlersModern idiomatic Rust, composable middleware

For new projects in 2026, Axum is the default recommendation (maintained by the tokio team, simpler mental model). Actix-web is still the fastest but slightly heavier API surface.

Step 1 — Install Rust (on your laptop)

bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
rustc --version

Step 2 — Create a minimal Axum API

bash
cargo new --bin api && cd api

Cargo.toml:

toml
[package]
name = "api"
version = "0.1.0"
edition = "2021"

[dependencies]
axum = "0.7"
tokio = { version = "1.36", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tower-http = { version = "0.5", features = ["trace"] }
tracing = "0.1"
tracing-subscriber = "0.3"
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres", "chrono"] }

src/main.rs:

rust
use axum::{routing::get, Json, Router};
use serde::Serialize;
use std::net::SocketAddr;

#[derive(Serialize)]
struct Health { status: &'static str, version: &'static str }

async fn health() -> Json<Health> {
    Json(Health { status: "ok", version: env!("CARGO_PKG_VERSION") })
}

#[tokio::main]
async fn main() {
    tracing_subscriber::fmt::init();

    let app = Router::new()
        .route("/health", get(health))
        .layer(tower_http::trace::TraceLayer::new_for_http());

    let addr: SocketAddr = "0.0.0.0:8080".parse().unwrap();
    let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
    tracing::info!("listening on {}", addr);
    axum::serve(listener, app).await.unwrap();
}

Run locally:

bash
RUST_LOG=info cargo run
curl http://localhost:8080/health

Step 3 — Cross-compile for Linux

On macOS/Windows:

bash
# Add target
rustup target add x86_64-unknown-linux-musl

# Ubuntu/Debian laptop:
sudo apt install musl-tools

# macOS:
brew install FiloSottile/musl-cross/musl-cross

Build static binary (works on any Linux without glibc fuss):

bash
cargo build --release --target x86_64-unknown-linux-musl
# Output: target/x86_64-unknown-linux-musl/release/api

Typical binary size: 5–15 MB. strip it further:

bash
strip target/x86_64-unknown-linux-musl/release/api

Upload:

bash
scp target/x86_64-unknown-linux-musl/release/api root@vps:/opt/rust-app/api

Step 4 — systemd on VPS

/etc/systemd/system/rust-api.service:

ini
[Unit]
Description=Rust API (Axum)
After=network.target

[Service]
Type=simple
User=rustapp
Group=rustapp
WorkingDirectory=/opt/rust-app
ExecStart=/opt/rust-app/api
Restart=on-failure
RestartSec=3

Environment="RUST_LOG=info"
Environment="DATABASE_URL=postgres://rustapp:pass@localhost/myapp"
EnvironmentFile=-/opt/rust-app/.env

# Hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=/opt/rust-app/data
ProtectHome=true

# Logging
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

Create user + start:

bash
sudo useradd -r -s /bin/false -d /opt/rust-app rustapp
sudo chown -R rustapp:rustapp /opt/rust-app
sudo systemctl daemon-reload
sudo systemctl enable --now rust-api
sudo journalctl -u rust-api -f

Step 5 — nginx + Let's Encrypt

Same pattern as Go/Java:

nginx
upstream rust_api { server 127.0.0.1:8080; keepalive 64; }

server {
    listen 80;
    server_name api.yourcompany.com;
    location / {
        proxy_pass http://rust_api;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Connection "";
    }
}
bash
sudo certbot --nginx -d api.yourcompany.com

Database with sqlx (compile-time SQL)

sqlx validates SQL queries at compile time against your actual schema.

rust
use sqlx::postgres::PgPoolOptions;

let pool = PgPoolOptions::new()
    .max_connections(10)
    .connect(&std::env::var("DATABASE_URL").unwrap())
    .await?;

let users = sqlx::query!("SELECT id, email FROM users WHERE active = true")
    .fetch_all(&pool)
    .await?;

If the schema doesn't match, compilation fails. No runtime surprises.

Async runtimes — Tokio is the answer

Rust has multiple async runtimes (tokio, async-std, smol). Tokio is the de-facto standard; both Axum and Actix-web use it. Don't pick anything else for web.

Configure tokio's threads:

rust
#[tokio::main(worker_threads = 4)]
async fn main() { ... }

Default is "one worker per CPU core" — usually fine.

Zero-downtime deployments

Rust binaries upgrade well with a blue-green pattern (same as Go):

bash
# Build new
scp new-api root@vps:/opt/rust-app/api-new

# Swap + restart
ssh root@vps "mv /opt/rust-app/api /opt/rust-app/api-old && 
              mv /opt/rust-app/api-new /opt/rust-app/api && 
              systemctl restart rust-api"

Systemd's Restart=on-failure kicks in if the new binary crashes — revert manually.

Or use systemd-notify + sd_notify_with_fds for true zero-downtime (complex; usually overkill).

Common pitfalls

FAQ

Q Is Rust production-ready for web apps?

Yes — Cloudflare, Discord, Amazon, Meta, and many others use Rust in production web stacks. Actix-web and Axum are battle-tested.

Q Rust vs Go for APIs?

Go compiles 10× faster and has a gentler learning curve. Rust has stronger type safety, slightly better throughput, and lower RAM. For a team learning one language: Go. For a performance-critical service: Rust.

Q Do I need Tokio? Can't I use sync Rust?

You can write blocking HTTP servers (e.g. with hyper's sync API) but modern Rust web ecosystem is async-first. Embrace it.

Q How much RAM does a Rust API need?

50–200 MB for a typical CRUD API. Our 2 GB VPS Starter comfortably runs 5+ Rust services.

Q Rust compilation is slow — how to speed up dev loop?

Use cargo watch -x run for auto-rebuild. Install the mold linker (10× faster link step). On laptops, enable rust-analyzer for fast IDE feedback without full compiles.

Rust loves a small, fast VPS. Start with a VPS Starter plan. Pick a VPS

Was this article helpful?

Your feedback helps us improve our documentation

Still need help? Submit a support ticket

Rust Web Apps on DomainIndia VPS — Actix-web, Axum, sqlx