How to Install Rust on Ubuntu 24.04 VPS: rustup, Cargo, and systemd Deploy
Rust has become the language of choice for building fast, memory-safe services that sit at the critical path of modern infrastructure — web servers, message brokers, API gateways, CLI tooling, and embedded agents. Its zero-cost abstractions, strict ownership model, and fearless concurrency make it ideal for long-running VPS workloads where every megabyte of RAM and every microsecond of latency matters. This guide walks you through installing Rust on an Ubuntu 24.04 VPS via the official rustup toolchain manager, then building, optimizing, cross-compiling, and deploying a compiled binary under a systemd unit.
Recommended Plan: CloudCore Starter gives you 4 vCPU, 8 GB RAM, and NVMe storage — plenty for compiling mid-sized Rust workspaces without swapping.
Table of Contents
Why Rust on a VPS?
Rust compiles to native machine code with no runtime interpreter, no garbage collector, and no JIT warm-up. That translates directly into practical wins on a VPS:
- Tiny memory footprint — A typical Axum or Actix web service idles at 5-15 MB of resident memory, compared to 80-150 MB for Node.js or 200+ MB for a JVM service. You can comfortably run multiple independent Rust services on a 4 GB VPS.
- No cold-start penalty — The compiled binary starts in milliseconds. There is nothing to warm up before it can serve its first request.
- Predictable latency — No GC pauses means p99 latency tracks p50 latency closely. This matters for SLAs, real-time APIs, and anything sitting behind a load balancer.
- Memory safety without GC — The borrow checker eliminates entire classes of bugs (use-after-free, data races, buffer overflows) at compile time, which reduces the blast radius of security vulnerabilities on an exposed VPS.
- Fearless concurrency — Async Rust with Tokio scales to tens of thousands of concurrent connections on a single-digit vCPU VPS, making it a strong fit for proxies, gateways, and WebSocket servers.
- Single static binary — Compiled with
musl, a Rust binary has zero runtime dependencies.scpit to the server and run it. No virtualenv, nonode_modules, no JAR loader.
Prerequisites
Before you begin, make sure you have:
- A VPS running Ubuntu 24.04 LTS with root or sudo access
- SSH access to your server
- At least 2 GB of RAM for compiling medium-sized crates (4 GB+ recommended; large dependency trees like
tokio+reqwest+serdecan spike to 1.5 GB during linking) - At least 5 GB of free disk space for the toolchain, source, and
target/build artefacts (Rust builds can grow quickly — budget 10 GB+ for active development)
ssh root@your-server-ipStep 1: Update System Packages
Refresh the package index and install the latest security updates before adding anything new:
sudo apt update && sudo apt upgrade -yIf the kernel or libc was updated, reboot before continuing:
sudo rebootThen reconnect via SSH.
Step 2: Install Build Dependencies
rustup itself only needs curl, but almost every non-trivial Rust crate pulls in cc, pkg-config, and OpenSSL headers at build time. Install the full toolchain prerequisites up front so you do not have to backtrack when a cargo build fails halfway through:
sudo apt install -y build-essential curl pkg-config libssl-dev ca-certificates gitPackage roles:
build-essential— GCC,make, and glibc headers. Rust invokes the system linker (cc) for the final linking step.pkg-config— Used by-syscrates (bindings to C libraries) to locate.pcfiles for things likelibssl,libpq, andlibsqlite3.libssl-dev— Required byopenssl-sys, which is transitively pulled in byreqwest,hyper-tls, and anything that opens an HTTPS connection. You can skip this if every dependency supportsrustlsinstead (see troubleshooting below).git— Needed forcargoto fetch crate sources from Git dependencies.
tonic for gRPC), also install the protobuf compiler:sudo apt install -y protobuf-compilerStep 3: Install Rust via rustup
rustup is the official toolchain manager. It installs Rust into $HOME/.rustup, keeps multiple toolchains (stable, beta, nightly) side by side, and manages cross-compilation targets. Do not install the rustc package from apt — it ships outdated versions, conflicts with rustup, and has no mechanism for updates.
Run the official installer as your regular user (not root):
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | shThe --proto '=https' --tlsv1.2 flags ensure curl refuses to downgrade to an insecure connection — this matters because you are piping shell code directly to sh.
You will see a prompt:
Welcome to Rust!This will download and install the official compiler for the Rust programming language, and its package manager, Cargo.
Current installation options:
default host triple: x86_64-unknown-linux-gnu default toolchain: stable (default) profile: default modify PATH variable: yes
1) Proceed with standard installation (default - just press enter) 2) Customize installation 3) Cancel installation >
Press Enter to accept defaults. The installer will:
rustup, cargo, rustc, rust-std, rust-docs, clippy, and rustfmt into $HOME/.cargo and $HOME/.rustup$HOME/.cargo/bin to your PATH by appending a line to ~/.profile, ~/.bashrc, and ~/.zshenvActivate the new PATH in your current shell without logging out:
source "$HOME/.cargo/env"Step 4: Verify the Installation
Check each component independently:
rustc --version
cargo --version
rustup --versionExpected output:
rustc 1.83.0 (90b35a623 2024-11-26)
cargo 1.83.0 (5ffbef321 2024-10-29)
rustup 1.27.1 (54dd3d00f 2024-04-24)Show where each binary lives:
which rustc cargo rustupExpected:
/home/youruser/.cargo/bin/rustc
/home/youruser/.cargo/bin/cargo
/home/youruser/.cargo/bin/rustupList everything rustup currently has installed:
rustup showThis prints the active toolchain, installed components, and available targets. At this point you should see a single stable-x86_64-unknown-linux-gnu entry.
Step 5: Manage Toolchains (Stable and Nightly)
rustup lets you keep multiple Rust versions on the same machine and switch between them per-directory. For production services, stable is the right default. For experimenting with unstable features (inline ASM, specialization, const generics expansions), you will need nightly.
Install nightly alongside stable:
rustup toolchain install nightlySet the default toolchain (affects all projects unless overridden):
rustup default stablePin a specific project to nightly without changing the global default — run this inside the project directory:
rustup override set nightlyThis creates a rust-toolchain.toml entry that cargo honours. You can also commit a rust-toolchain.toml file to lock the team to one version:
[toolchain]
channel = "1.83.0"
components = ["rustfmt", "clippy"]
targets = ["x86_64-unknown-linux-musl"]Run a one-off command with a different toolchain without switching:
cargo +nightly buildStep 6: Cargo Basics — new, build, run, test
cargo is Rust's build tool, dependency manager, test runner, and documentation generator. Everything you need for a project lives inside it.
Create a new binary project:
cargo new hello-vps
cd hello-vpsThis scaffolds:
hello-vps/
├── Cargo.toml
├── .gitignore
└── src/
└── main.rsCargo.toml is the manifest (dependencies, metadata, build profiles). src/main.rs contains a trivial "Hello, world!" program.
Build in debug mode (fast compile, unoptimized binary — use during development):
cargo buildThe compiled binary lands at target/debug/hello-vps.
Build and run in one step:
cargo runExpected output:
Compiling hello-vps v0.1.0 (/root/hello-vps)
Finished dev profile [unoptimized + debuginfo] target(s) in 0.52s
Running target/debug/hello-vps
Hello, world!Run tests (writes the binary to target/debug/deps/):
cargo testCheck for errors without emitting a binary — roughly 3-5x faster than cargo build, ideal for editor-driven workflows:
cargo checkFormat the entire workspace to the official style:
cargo fmtRun the linter for idiomatic-code warnings and common mistakes:
cargo clippy -- -D warningsThe -D warnings flag promotes every clippy warning to a hard error — useful in CI pipelines where you want to fail the build on lint regressions.
Generate and open the API documentation for your crate and all its dependencies:
cargo doc --openStep 7: Optimized Release Profile
Debug builds prioritize compile speed. For anything you deploy, build in release mode:
cargo build --releaseThis writes to target/release/hello-vps with -O optimizations enabled. The binary is typically 5-20x faster than the debug version.
To squeeze out additional performance and shrink the binary further, customize the release profile in Cargo.toml:
[profile.release]
opt-level = 3
lto = "fat"
codegen-units = 1
strip = "symbols"
panic = "abort"What each setting does:
opt-level = 3— Maximum optimization. The default for--release, listed here for clarity.lto = "fat"— Link-Time Optimization across all crates in the dependency graph. Adds 30-90 seconds to link time but commonly yields 5-15% runtime speedup and a smaller binary. Use"thin"for 80% of the benefit at a fraction of the link cost.codegen-units = 1— Compile the crate as a single translation unit. Disables parallel codegen (so builds are slower) but lets the optimizer see the whole crate at once.strip = "symbols"— Strip debug and symbol information from the final binary. Cuts the binary size by 50-80% and is safe for production where you keep the debuginfo offline for crash analysis.panic = "abort"— Skip the unwinding runtime on panic. Smaller binaries and slightly faster, but panics become immediateSIGABRTrather than unwinding stacks — only use if you do not rely oncatch_unwind.
Step 8: Cross-Compile to musl for Static Binaries
The default Linux target, x86_64-unknown-linux-gnu, dynamically links against the system glibc. That binary will not run on a server with an older glibc (classic "GLIBC_2.39 not found" error when moving from Ubuntu 24.04 to Debian 11). The fix is to compile against musl — a minimal libc that statically links into your binary.
A Rust binary built for x86_64-unknown-linux-musl is a single file you can scp to any x86_64 Linux machine, from Alpine containers to ancient CentOS 7 boxes, and it will just run.
Add the musl target:
rustup target add x86_64-unknown-linux-muslInstall the musl C toolchain (needed by the linker and by any -sys crates that call out to a C compiler):
sudo apt install -y musl-toolsBuild:
cargo build --release --target x86_64-unknown-linux-muslThe static binary lands at target/x86_64-unknown-linux-musl/release/hello-vps. Confirm it has no dynamic dependencies:
ldd target/x86_64-unknown-linux-musl/release/hello-vpsExpected output:
statically linkedNote on OpenSSL: If your crate pulls in openssl-sys, cross-compiling to musl fails unless you supply a musl-built OpenSSL. The easier fix is to switch to rustls, a pure-Rust TLS implementation — for example, with reqwest:
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] }Step 9: Deploy a Rust Binary with systemd
The standard deploy pattern for a Rust service on a VPS:
cargo build --release --target x86_64-unknown-linux-muslscp the single binary to the target VPSCopy the binary to /usr/local/bin:
sudo install -m 755 target/x86_64-unknown-linux-musl/release/hello-vps /usr/local/bin/hello-vpsinstall sets the permissions in one step and will overwrite safely on future deploys.
Create a dedicated system user so the service does not run as root:
sudo useradd --system --no-create-home --shell /usr/sbin/nologin hellovpsWrite the systemd unit:
sudo tee /etc/systemd/system/hello-vps.service > /dev/null <<'EOF' [Unit] Description=Hello VPS - Rust service After=network-online.target Wants=network-online.target[Service] Type=simple User=hellovps Group=hellovps ExecStart=/usr/local/bin/hello-vps Restart=on-failure RestartSec=5s Environment="RUST_LOG=info" Environment="PORT=8080"
Hardening
NoNewPrivileges=true PrivateTmp=true ProtectSystem=strict ProtectHome=true ProtectKernelTunables=true ProtectKernelModules=true ProtectControlGroups=true LockPersonality=true RestrictRealtime=true RestrictSUIDSGID=trueResource limits
LimitNOFILE=65535 MemoryMax=512M
[Install] WantedBy=multi-user.target EOF
Reload systemd, enable the unit for boot, and start it:
sudo systemctl daemon-reload
sudo systemctl enable --now hello-vpsConfirm it is running:
sudo systemctl status hello-vpsTail logs:
sudo journalctl -u hello-vps -fFor a public-facing HTTP service, front it with Nginx as a reverse proxy with TLS — see our Nginx install and configuration guide and the managing services with systemd walkthrough for deeper coverage.
CI-Based Deploy Pattern
A minimal GitHub Actions deploy step looks like:
- run: cargo build --release --target x86_64-unknown-linux-musl
- run: |
scp target/x86_64-unknown-linux-musl/release/hello-vps deploy@vps:/tmp/hello-vps
ssh deploy@vps 'sudo install -m 755 /tmp/hello-vps /usr/local/bin/hello-vps && sudo systemctl restart hello-vps'Because the binary is self-contained, there is no Docker image to push, no registry to authenticate against, and no runtime to configure on the target host.
Step 10: Popular Rust Frameworks
A few crates dominate the server-side Rust ecosystem. All four integrate cleanly with the systemd deploy pattern above.
- Axum — Tokio's first-party web framework. Built on
hyperandtower, with a tower-ecosystem middleware story (tracing, auth, rate limiting, compression). Currently the default choice for new Rust HTTP services. - Actix Web — Actor-based, extremely high-throughput framework. Consistently tops the TechEmpower benchmarks. Strong ecosystem for websockets, extractors, and HTTP/2.
- Rocket — Ergonomic, macro-heavy framework with a strong focus on developer experience. Compile-time request validation, typed URL params, and guard-based auth. Requires nightly for some features historically, now mostly stable.
- Tokio — Not a framework itself but the async runtime underneath Axum, Actix, and most of the async ecosystem. Provides the scheduler, I/O primitives, timers, and synchronization utilities. You will import it directly for custom networking code, TCP servers, or background workers.
cargo add axum tokio --features tokio/fullDeveloper Productivity: cargo-watch, rust-analyzer, Workspaces
Hot Reload with cargo-watch
cargo-watch re-runs a command whenever source files change. Install it once:
cargo install cargo-watchThen in your project:
cargo watch -x runEvery save triggers cargo run. Swap in cargo watch -x 'test --lib' for TDD-style loops.
rust-analyzer
rust-analyzer is the Language Server Protocol implementation for Rust. Install the component via rustup:
rustup component add rust-analyzerThen point your editor at it:
- VS Code — install the official "rust-analyzer" extension
- Neovim — use
nvim-lspconfigwith therust_analyzerserver - Helix — zero config, it is detected automatically
Workspace Layout
Once a project grows past one binary plus a library, switch to a Cargo workspace. A typical layout:
myservice/
├── Cargo.toml # workspace manifest
├── Cargo.lock
├── crates/
│ ├── api/ # HTTP server (binary)
│ │ ├── Cargo.toml
│ │ └── src/
│ ├── core/ # business logic (library)
│ │ ├── Cargo.toml
│ │ └── src/
│ └── migrations/ # DB migration runner (binary)
│ ├── Cargo.toml
│ └── src/
└── target/ # shared across all cratesRoot Cargo.toml:
[workspace] resolver = "2" members = ["crates/*"]
[workspace.dependencies] tokio = { version = "1", features = ["full"] } serde = { version = "1", features = ["derive"] }
Member crates reference shared deps with tokio.workspace = true, which keeps versions aligned across the whole tree. cargo build --workspace compiles everything; cargo build -p api compiles just one member.
Upgrading Rust
rustup makes upgrades painless. Update every installed toolchain to the latest release:
rustup updateExpected output:
info: syncing channel updates for 'stable-x86_64-unknown-linux-gnu'
info: latest update on 2026-02-20, rust version 1.85.0 (abcdef123 2026-02-18)
...
stable-x86_64-unknown-linux-gnu updated - rustc 1.85.0 (from 1.83.0)Update just one channel:
rustup update stableUpdate rustup itself:
rustup self updateRust ships a new stable release every six weeks. The upgrade is almost always drop-in — the stability promise means code that compiled on 1.83 compiles on 1.85 unless you opted into #[feature(...)] flags on nightly.
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
error: linker cc not found | build-essential missing | sudo apt install -y build-essential |
Could not find directory of OpenSSL installation | openssl-sys cannot locate libssl headers | Install libssl-dev and pkg-config, or switch to rustls with default-features = false, features = ["rustls-tls"] |
Could not find protoc installation | tonic-build or prost-build needs the protobuf compiler | sudo apt install -y protobuf-compiler |
error[E0463]: can't find crate for 'std' when cross-compiling | Target not installed | rustup target add x86_64-unknown-linux-musl (or your target triple) |
linker 'musl-gcc' not found | musl toolchain missing | sudo apt install -y musl-tools |
| Slow incremental rebuilds | Debug info recompilation, default linker | Set CARGO_INCREMENTAL=1 (default on debug), switch linker to lld or mold via a .cargo/config.toml with [target.x86_64-unknown-linux-gnu] linker = "clang", rustflags = ["-C", "link-arg=-fuse-ld=lld"] |
the trait 'Send' is not implemented for 'Rc<...>' in an async context | Using non-Send types across .await points | Switch Rc to Arc, RefCell to Mutex, or pin the future to the current thread with a LocalSet |
Out of memory during cargo build on a 2 GB VPS | Linking a release binary with LTO spikes memory | Temporarily drop lto = "fat" to lto = "thin", reduce codegen-units less aggressively, or add a swap file: sudo fallocate -l 4G /swapfile && sudo chmod 600 /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile |
warning: unused manifest key: package.edition | Old Cargo.toml using cargo-features that shipped stable | Run cargo fix --edition to migrate to the latest edition |
Viewing Detailed Build Errors
When a build fails and the error is unclear, ask cargo for a verbose trace:
cargo build --release -vvFor cryptic linker errors, show the exact linker invocation:
RUSTFLAGS="--print link-args" cargo build --releaseNext Steps
With Rust installed and a binary deployed under systemd, here are natural follow-ups:
- Put your service behind Nginx with TLS — Terminate HTTPS at the edge and forward to your Rust binary over localhost. See how to install Nginx on Ubuntu 24.04.
- Add structured logging and tracing — The
tracingcrate plustracing-subscribergives you JSON logs, spans, and distributed tracing with minimal boilerplate. - Browse the official book — The Rust Programming Language is the canonical free textbook. Chapters 13-20 are especially valuable once you are past "Hello, world!".
- Explore the toolchain reference —
rustup.rsandrust-lang.orgcover every knob mentioned here in depth. - Deploy a real async service — Try building a small Axum API with SQLx and Postgres, cross-compile it to musl, and ship it to a VPS.
Deploy Your Rust Service on CloudCore Starter>
4 vCPU, 8 GB RAM, NVMe SSD — enough headroom to compile mid-sized workspaces and run multiple Rust services side by side. Predictable monthly pricing, no egress fees, provisioned in under 60 seconds.>
Launch a CloudCore Starter VPS and scp your first Rust binary tonight.