Client Area

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

ByDomain India Team·DomainIndia Engineering
5 min read24 Apr 20264 views
# 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 { 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

Still need help?

Our support team can assist you directly.

Submit Ticket