Rust Web Applications on DomainIndia VPS: Actix-web and Axum
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
| Framework | Async runtime | Style | Best for |
|---|---|---|---|
| Actix-web | tokio | Actor model, middleware chains | High-throughput APIs, raw performance |
| Axum | tokio | Tower-based, type-safe handlers | Modern 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)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
rustc --versionStep 2 — Create a minimal Axum API
cargo new --bin api && cd apiCargo.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:
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:
RUST_LOG=info cargo run
curl http://localhost:8080/healthStep 3 — Cross-compile for Linux
On macOS/Windows:
# 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-crossBuild static binary (works on any Linux without glibc fuss):
cargo build --release --target x86_64-unknown-linux-musl
# Output: target/x86_64-unknown-linux-musl/release/apiTypical binary size: 5–15 MB. strip it further:
strip target/x86_64-unknown-linux-musl/release/apiUpload:
scp target/x86_64-unknown-linux-musl/release/api root@vps:/opt/rust-app/apiStep 4 — systemd on VPS
/etc/systemd/system/rust-api.service:
[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.targetCreate user + start:
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 -fStep 5 — nginx + Let's Encrypt
Same pattern as Go/Java:
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 "";
}
}sudo certbot --nginx -d api.yourcompany.comDatabase with sqlx (compile-time SQL)
sqlx validates SQL queries at compile time against your actual schema.
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:
#[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):
# 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
Yes — Cloudflare, Discord, Amazon, Meta, and many others use Rust in production web stacks. Actix-web and Axum are battle-tested.
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.
You can write blocking HTTP servers (e.g. with hyper's sync API) but modern Rust web ecosystem is async-first. Embrace it.
50–200 MB for a typical CRUD API. Our 2 GB VPS Starter comfortably runs 5+ Rust services.
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