☢

Hosting a Compiled Binary as a systemd Daemon (Rust, Go, C++)

Use case: You built a web service in Rust, Go, C++, or any compiled language and need it running persistently on a Linux server - auto-starting on boot, auto-restarting on crash, with proper logging.


1. Upload the Binary to the Server

# From your local machine
scp ./web-server user@your-server:/usr/local/bin/web-server

# Or build directly on the server, then move to a standard location
sudo mv ./web-server /usr/local/bin/web-server
sudo chmod +x /usr/local/bin/web-server

/usr/local/bin/ is the conventional location for manually installed binaries. Alternatively use /opt/your-app/ for self-contained apps with config files alongside the binary.


Never run a service as root. Create a minimal system user with no login shell and no home directory:

sudo useradd --system --no-create-home --shell /usr/sbin/nologin webserver

Set ownership if the binary or working directory needs it:

sudo chown webserver:webserver /usr/local/bin/web-server

3. Create the systemd Unit File

sudo nano /etc/systemd/system/web-server.service
[Unit]
Description=My Web Server
Documentation=https://github.com/you/your-repo
After=network.target
# Use After=network-online.target if the service needs actual network connectivity at start

[Service]
Type=simple
ExecStart=/usr/local/bin/web-server --port 5689
WorkingDirectory=/opt/web-server
User=webserver
Group=webserver
Restart=always
RestartSec=5

# Environment variables
Environment=APP_ENV=production
Environment=LOG_LEVEL=info
# Or load from a file (keep secrets out of the unit file)
# EnvironmentFile=/etc/web-server/env

# Resource limits (optional but recommended)
LimitNOFILE=65536

# Prevent the service from writing outside its working directory (optional hardening)
# PrivateTmp=true
# NoNewPrivileges=true
# ReadOnlyPaths=/

[Install]
WantedBy=multi-user.target

Key directives explained

Directive What it does
After=network.target Wait for network stack before starting
Type=simple Process started by ExecStart is the main process. Use Type=forking if the binary daemonizes itself
Restart=always Restart on any exit - crash, OOM kill, etc.
RestartSec=5 Wait 5 seconds before restarting (prevents tight crash loops)
EnvironmentFile= Load env vars from a file - keep secrets out of the unit file
LimitNOFILE=65536 Raise open file descriptor limit (important for high-concurrency servers)

Restart policies

Value Restarts on
no Never (default)
always Any exit - clean or crash
on-failure Non-zero exit code or signal only
on-abnormal Signals, watchdog timeout, but not clean exit

For a web server, Restart=always or Restart=on-failure are both reasonable choices.


4. Enable and Start

# Reload systemd to pick up the new unit file
sudo systemctl daemon-reload

# Enable: auto-start on boot
sudo systemctl enable web-server

# Start now
sudo systemctl start web-server

# Check status
sudo systemctl status web-server

Expected output:

● web-server.service - My Web Server
     Loaded: loaded (/etc/systemd/system/web-server.service; enabled)
     Active: active (running) since ...
   Main PID: 12345 (web-server)

5. Logs

systemd captures stdout/stderr automatically via journald:

# Follow live logs
sudo journalctl -u web-server -f

# Last 100 lines
sudo journalctl -u web-server -n 100

# Logs since last boot
sudo journalctl -u web-server -b

# Logs between timestamps
sudo journalctl -u web-server --since "2024-01-01 00:00" --until "2024-01-01 06:00"

6. Managing the Service

sudo systemctl stop web-server        # stop
sudo systemctl restart web-server     # restart
sudo systemctl reload web-server      # send SIGHUP (if app supports graceful reload)
sudo systemctl disable web-server     # remove from boot (doesn't stop running service)
sudo systemctl is-active web-server   # prints "active" or "inactive"
sudo systemctl is-enabled web-server  # prints "enabled" or "disabled"

7. Updating the Binary

# Upload new binary
scp ./web-server user@your-server:/usr/local/bin/web-server

# Restart the service to load the new binary
sudo systemctl restart web-server
sudo systemctl status web-server

For zero-downtime deploys, consider running two instances behind a reverse proxy and switching traffic, or use a binary that supports graceful shutdown on SIGTERM.


8. Reverse Proxy (Nginx)

Compiled binaries typically bind to a local port. Use Nginx to expose them on 80/443:

sudo nano /etc/nginx/sites-available/web-server
server {
    listen 80;
    server_name yourdomain.com;

    location / {
        proxy_pass http://127.0.0.1:5689;
        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;

        # WebSocket support (remove if not needed)
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}
sudo ln -s /etc/nginx/sites-available/web-server /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Then add SSL with Certbot:

sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d yourdomain.com

Troubleshooting

# Service fails to start - check the full error
sudo journalctl -u web-server -n 50 --no-pager

# Binary not executable
sudo chmod +x /usr/local/bin/web-server

# Wrong architecture (e.g. ARM binary on x86)
file /usr/local/bin/web-server
# Should match: ELF 64-bit LSB executable, x86-64 (or aarch64 for ARM)

# Port already in use
sudo ss -tlnp | grep 5689

# Permission denied on working directory
sudo chown -R webserver:webserver /opt/web-server

Notes