Rust compiles to small, fast, memory-safe binaries that need no runtime on the server, which makes it a good fit for high-throughput APIs on a small VPS. This guide covers choosing between Axum and Actix-web, building a Linux binary, running it under systemd and putting nginx with free SSL in front, on a Domain India VPS.
Use Axum for most new Rust web projects, or Actix-web if your team already knows it. Build a release binary for Linux (a static musl build is easiest to copy), run it under systemd as its own user bound to 127.0.0.1, and put nginx with a Let's Encrypt certificate in front. Rust apps are long-running processes, so on Domain India run them on a VPS, or on the App Platform from your own Dockerfile.
1. Why Rust for web apps
- Performance: high throughput and low, predictable latency
- Memory: a typical Rust API uses tens of MB of RAM, much less than a JVM app
- Safety: no null pointer dereferences or data races in safe code, checked at compile time
- Ecosystem: mature web frameworks and a stable async runtime (Tokio)
Trade-offs:
- A steep learning curve; the borrow checker takes time to get comfortable with
- Longer compile times than Go
- A smaller hiring pool than PHP, Node.js or Python
Good fit: performance-critical APIs, proxies, data-processing services. For a simple CRUD app with modest traffic, Node.js, Laravel or Django are usually faster to ship.
2. Actix-web or Axum
| Framework | Async runtime | Style | Best for |
|---|---|---|---|
| Actix-web | Tokio (with its own actor-based system) | Mature, batteries included | Teams already using it, raw throughput |
| Axum | Tokio | Tower-based, extractor handlers | New projects, composable middleware |
For new projects, Axum is the common recommendation: it is maintained by the Tokio team, and its middleware comes from the wider Tower ecosystem. Actix-web is equally production-ready and very fast.
3. Install Rust (on your computer)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
rustc --version4. Create a minimal Axum API
cargo new api && cd apiCargo.toml:
[package]
name = "api"
version = "0.1.0"
edition = "2024"
[dependencies]
axum = "0.8"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tower-http = { version = "0.6", features = ["trace"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
sqlx = { version = "0.8", features = ["runtime-tokio", "tls-rustls", "postgres", "chrono"] }
[profile.release]
strip = truesrc/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()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.init();
let app = Router::new()
.route("/health", get(health))
.layer(tower_http::trace::TraceLayer::new_for_http());
// Listen on localhost only; nginx is the public entry point
let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
tracing::info!("listening on {}", addr);
axum::serve(listener, app).await.unwrap();
}Run it locally:
RUST_LOG=info cargo run
curl http://localhost:8080/healthNote for Axum 0.8: path parameters use braces, for example /users/{id}.
5. Build a Linux binary
The simplest option is a static binary using the musl target, which runs on any x86-64 Linux without worrying about the glibc version:
rustup target add x86_64-unknown-linux-musl
# On a Linux build machine (Debian/Ubuntu)
sudo apt install musl-tools
cargo build --release --target x86_64-unknown-linux-musl
# Output: target/x86_64-unknown-linux-musl/release/apiFrom macOS or Windows, use cargo zigbuild (with Zig installed) or cross (with Docker) to build for the musl target, or build in CI on Linux. With strip = true in the release profile, a typical API binary is a few MB to about 15 MB.
musl's memory allocator is slower than glibc's under heavy multi-threaded load. If that matters, build for x86_64-unknown-linux-gnu on the same Linux distribution as your server, or use an allocator such as mimalloc.
Upload it:
ssh root@your-vps 'mkdir -p /opt/rust-app/data'
scp target/x86_64-unknown-linux-musl/release/api root@your-vps:/opt/rust-app/api6. Run it under systemd
/etc/systemd/system/rust-api.service:
[Unit]
Description=Rust API (Axum)
After=network-online.target
Wants=network-online.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"
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.targetPut secrets such as DATABASE_URL=postgres://rustapp:your-password@localhost/myapp in /opt/rust-app/.env, not in the unit file, and chmod 600 it.
Create the user and start the service:
sudo useradd -r -s /sbin/nologin -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 -f7. nginx and Let's Encrypt
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 X-Forwarded-Proto $scheme;
proxy_set_header Connection "";
}
}sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d api.yourcompany.comOn AlmaLinux, certbot comes from EPEL, and SELinux blocks nginx from reaching your app until you run sudo setsebool -P httpd_can_network_connect 1 (without it you get a 502).
8. Database access with sqlx
sqlx can check your SQL at compile time against the real database schema:
use sqlx::postgres::PgPoolOptions;
let pool = PgPoolOptions::new()
.max_connections(10)
.connect(&std::env::var("DATABASE_URL")?)
.await?;
let users = sqlx::query!("SELECT id, email FROM users WHERE active = true")
.fetch_all(&pool)
.await?;If a query doesn't match the schema, the build fails instead of the request. For builds without database access (CI, Docker), run cargo sqlx prepare and commit the generated .sqlx folder.
9. Async runtime: Tokio
Tokio is the standard async runtime for Rust web services; both Axum and Actix-web use it. The default starts one worker thread per CPU core, which is usually right. To set it yourself:
#[tokio::main(flavor = "multi_thread", worker_threads = 4)]
async fn main() { /* ... */ }Move CPU-heavy work off the async threads with tokio::task::spawn_blocking.
10. Deploying new versions
A Rust service is a single file, so upgrades are simple. Upload the new binary next to the old one, then swap and restart:
scp target/x86_64-unknown-linux-musl/release/api root@your-vps:/opt/rust-app/api-new
ssh root@your-vps 'cd /opt/rust-app && cp api api-old && mv api-new api && chown rustapp:rustapp api && systemctl restart rust-api'The restart takes a fraction of a second, but in-flight requests are dropped. Roll back by moving api-old back. For truly zero-downtime restarts, use systemd socket activation (the listenfd crate) or run two instances behind nginx and restart them one at a time.
11. Common pitfalls
12. Running Rust on Domain India
- VPS: everything in this guide. Domain India VPS plans are self-managed with full root access on KVM, so you install nginx, PostgreSQL and certbot and keep the server patched yourself. Plans start from ₹553 a month, excluding 18% GST (Domain India list price on 30 September 2026). No backups or snapshots are included, so back up your data yourself.
- App Platform: runs a Rust app from your own Dockerfile (a multi-stage build that compiles, then copies the binary into a small image), deployed from GitHub with Deploy Now or with a deploy token. PostgreSQL and free SSL are included; there is no SSH, no Redis and no WebSocket support.
- Shared hosting: not suitable. It stops long-running processes, and its app tools are for Node.js and Python.
Is Rust production-ready for web apps?
Yes. Large companies run Rust in production web infrastructure, and both Axum and Actix-web are mature, widely used frameworks.
Rust or Go for APIs?
Go compiles faster and is quicker to learn. Rust gives stronger compile-time guarantees, no garbage-collection pauses and usually lower memory use. For a team learning its first systems language, Go is easier; for a performance-critical service, Rust is a strong choice.
Do I need Tokio?
For Axum and Actix-web, yes; they are built on it. The Rust web ecosystem is async-first, and Tokio is its standard runtime.
How much RAM does a Rust API need?
Often tens of MB for a typical CRUD API, rising with caches, connection pools and traffic. Measure your own service under load; you can usually run several Rust services on a small VPS.
How can I speed up the development loop?
Use cargo check for quick feedback, rust-analyzer in your editor, bacon or cargo-watch for automatic rebuilds, and a faster linker such as mold on Linux.
Can I run a Rust app on Domain India shared hosting?
No. Shared hosting stops long-running processes and has no Rust app tool. Use a Domain India VPS, or the App Platform with your own Dockerfile.
Ready to deploy? Pick a VPS plan to run your binary with full control, or build it into a container on the App Platform.
Rust binaries are small and fast, and a self-managed VPS gives you full control over how they run.
Pick a VPS