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
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
- Open Task Scheduler → Create Task
- General: name it
scd-server, select "Run whether user is logged on or not" - Triggers: New → At startup
- Actions: New → Start a program →
C:\scd-server\scd-server-win-x64.exe, Start in:C:\scd-server - 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
Secureflag is set automatically over HTTPS; - HSTS is sent automatically over HTTPS.
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.
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.