Skip to main content

Running as a Service

For production, run scd-server as a system service so it starts automatically on reboot and restarts if it crashes.


Default setup

Most teams run scd-server on a dedicated machine (or a VM) that is always on and reachable by all developers on the same network. With host: 0.0.0.0 in config.yml, developers connect directly using the server's LAN IP — no extra networking required.

If all developers are on the same machine as scd-server, use host: 127.0.0.1 instead.


Linux — systemd

systemd is the recommended service manager on Linux. The unit below is production-ready: dedicated system user, filesystem hardening, and automatic restart.

1. Create a system user

sudo useradd --system --no-create-home --shell /bin/false scd-server

2. Place the binary and data files

sudo mkdir -p /opt/scd-server/data
sudo cp scd-server-linux-x64 /opt/scd-server/
sudo cp license.key scd-public.pem /opt/scd-server/data/

3. Run init and edit config

Run --init in the terminal — it sets up config.yml and prompts you to set the admin password. The service in step 6 won't start until an admin account exists, so set it here first.

sudo /opt/scd-server/scd-server-linux-x64 --init
sudo nano /opt/scd-server/config.yml
Unattended install

Provisioning without an interactive terminal? Provide the password via the environment for the --init run instead: sudo SCD_ADMIN_PASSWORD='<strong-password>' /opt/scd-server/scd-server-linux-x64 --init. See First-run setup for details.

4. Set ownership and permissions

sudo chown -R scd-server:scd-server /opt/scd-server
sudo chmod 700 /opt/scd-server/data

The data/ directory holds scd.db, license.key, and scd-public.pem. The SQLite database is not password-protected at the file level, so filesystem permissions are the primary protection for its contents.

5. Create the service unit

Create /etc/systemd/system/scd-server.service:

[Unit]
Description=Secure Code by Design Server
Documentation=https://docs-server.securecodebydesign.com
After=network.target

[Service]
Type=simple
User=scd-server
Group=scd-server
WorkingDirectory=/opt/scd-server
ExecStart=/opt/scd-server/scd-server-linux-x64
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=scd-server
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ReadWritePaths=/opt/scd-server/data

[Install]
WantedBy=multi-user.target

6. Enable and start

sudo systemctl daemon-reload
sudo systemctl enable scd-server
sudo systemctl start scd-server
sudo systemctl status scd-server

View logs

sudo journalctl -u scd-server -f # live
sudo journalctl -u scd-server -n 100 # recent

Windows — NSSM

NSSM (Non-Sucking Service Manager) is the recommended way to run scd-server as a Windows service.

1. Install NSSM

Download NSSM from nssm.cc and place nssm.exe somewhere on your PATH (e.g. C:\Windows\System32).

2. Install the service

Open a command prompt as Administrator:

nssm install scd-server "C:\scd-server\scd-server-win-x64.exe"
nssm set scd-server AppDirectory "C:\scd-server"
nssm set scd-server DisplayName "Secure Code by Design Server"
nssm set scd-server Start SERVICE_AUTO_START
nssm start scd-server

Useful NSSM commands

nssm status scd-server
nssm restart scd-server
nssm stop scd-server
nssm remove scd-server confirm
Alternative: Windows Task Scheduler
  1. Open Task Scheduler → Create Task
  2. General: name it scd-server, select "Run whether user is logged on or not"
  3. Triggers: New → At startup
  4. Actions: New → Start a program → C:\scd-server\scd-server-win-x64.exe, Start in: C:\scd-server
  5. Settings: check "Restart if the task fails"

Task Scheduler gives less control over logging and restart behaviour than NSSM.


macOS

macOS service management for scd-server is on the roadmap. For now, run scd-server from a terminal session.


Advanced configuration

Bind to a specific address

To bind to a specific LAN or VPN interface instead of all interfaces:

host: 192.168.1.50
port: 3000

Reverse proxy and TLS

scd-server does not handle TLS directly. Terminate TLS with nginx or Caddy and proxy to localhost.

nginx example:

server {
listen 443 ssl;
server_name scd.example.internal;

ssl_certificate /etc/ssl/scd.example.internal.crt;
ssl_certificate_key /etc/ssl/scd.example.internal.key;

location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

Set host: 127.0.0.1 in config.yml so scd-server only accepts proxied connections.

Tell scd-server it's behind a proxy. When — and only when — a trusted reverse proxy sits in front, set SCD_TRUST_PROXY in the service environment (systemd: add Environment=SCD_TRUST_PROXY=1 to the [Service] block; 1 trusts a single proxy hop). With it set, scd-server reads the real client from X-Forwarded-For and the transport from X-Forwarded-Proto, which makes three things correct:

  • login rate-limiting counts real client IPs, not the proxy's single IP;
  • the session cookie's Secure flag is set automatically over HTTPS;
  • HSTS is sent automatically over HTTPS.
Don't set it without a proxy

Do not set SCD_TRUST_PROXY when scd-server is exposed directly (no proxy in front). Trusting X-Forwarded-For without a proxy lets a client spoof its IP and bypass the login rate limiter. Leaving it unset is the correct, safe default for direct exposure.

Remote access

scd-server is an internal tool — the whole point is that your code never leaves your network. The recommended way to make it reachable for developers off-site is a VPN, not an open internet port.

Tailscale is the simplest option: install Tailscale on the scd-server machine and on each developer machine. Each gets a stable Tailscale IP. Developers use that IP as --central-url in scd configure.

WireGuard is a self-hosted, open-source VPN with no coordination server.

Headscale is a self-hosted Tailscale coordination server — full control, no third-party service.

Boot ordering when binding to a VPN address

If host in config.yml is set to a VPN-assigned address (e.g. a Tailscale IP), the scd-server service must start after the VPN interface is up. On Linux, add the VPN service to the After= line in the systemd unit:

# Tailscale
After=network.target tailscaled.service

# WireGuard (e.g. wg0)
After=network.target wg-quick@wg0.service

Without this, scd-server may fail to bind on boot if the interface is not ready yet.