Skip to main content
workerd is designed to be unopinionated about how it runs. For production deployments on Linux, systemd provides robust process management, automatic restarts, and socket activation for privileged ports.

Why systemd?

systemd offers several advantages for production workerd deployments:
  • Socket activation: Open privileged ports (80, 443) without running as root
  • Process supervision: Automatic restart on crashes
  • Resource limits: Control CPU, memory, and file descriptor usage
  • Logging integration: Structured logs via journald
  • Security hardening: Sandboxing and capability restrictions

Basic deployment

Service file

Create /etc/systemd/system/workerd.service:
[Unit]
Description=workerd runtime
After=local-fs.target remote-fs.target network-online.target
Requires=local-fs.target remote-fs.target workerd.socket
Wants=network-online.target

[Service]
Type=exec
ExecStart=/usr/bin/workerd serve /etc/workerd/config.capnp --socket-fd http=3 --socket-fd https=4
Sockets=workerd.socket

# If workerd crashes, restart it.
Restart=always

# Run under an unprivileged user account.
User=nobody
Group=nogroup

# Hardening measure: Do not allow workerd to run suid-root programs.
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target

Socket file

Create /etc/systemd/system/workerd.socket:
[Unit]
Description=sockets for workerd
PartOf=workerd.service

[Socket]
ListenStream=0.0.0.0:80
ListenStream=0.0.0.0:443

[Install]
WantedBy=sockets.target

Configuration file

Create /etc/workerd/config.capnp:
using Workerd = import "/workerd/workerd.capnp";

const config :Workerd.Config = (
  services = [
    (name = "main", worker = .mainWorker),
  ],

  sockets = [
    # Socket passed from systemd via fd 3
    ( name = "http",
      address = "*:80",
      http = (),
      service = "main"
    ),
    # Socket passed from systemd via fd 4
    ( name = "https",
      address = "*:443",
      http = (
        tlsOptions = (
          keypair = (
            privateKey = embed "/etc/workerd/ssl/private.pem",
            certificateChain = embed "/etc/workerd/ssl/cert.pem",
          ),
        ),
      ),
      service = "main"
    ),
  ]
);

const mainWorker :Workerd.Worker = (
  serviceWorkerScript = embed "/etc/workerd/workers/main.js",
  compatibilityDate = "2024-01-01",
);

Enable and start

# Reload systemd configuration
sudo systemctl daemon-reload

# Enable socket activation (starts on boot)
sudo systemctl enable workerd.socket

# Start the socket
sudo systemctl start workerd.socket

# Check status
sudo systemctl status workerd.socket
sudo systemctl status workerd.service
The service starts automatically when the first connection is made to the socket, thanks to systemd’s socket activation.

Socket activation explained

The --socket-fd flag tells workerd to use file descriptors passed from systemd:
--socket-fd http=3 --socket-fd https=4
This maps:
  • File descriptor 3 → socket named “http” in config
  • File descriptor 4 → socket named “https” in config
Systemd opens the sockets with elevated privileges, then passes them to workerd running as an unprivileged user.

Security hardening

Additional service restrictions

Add these to your service file for enhanced security:
[Service]
# Existing configuration...

# Security hardening
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/workerd
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
RestrictNamespaces=true
LockPersonality=true
RestrictRealtime=true
RestrictSUIDSGID=true
RemoveIPC=true
PrivateMounts=true
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM

# Resource limits
LimitNOFILE=65536
LimitNPROC=512

Create dedicated user

Instead of using nobody, create a dedicated user:
# Create workerd user
sudo useradd --system --no-create-home --shell /bin/false workerd

# Update service file
# User=workerd
# Group=workerd

File permissions

# Set correct ownership
sudo chown -R workerd:workerd /etc/workerd
sudo chmod 750 /etc/workerd
sudo chmod 640 /etc/workerd/config.capnp

# Protect SSL keys
sudo chmod 600 /etc/workerd/ssl/private.pem

Resource limits

Memory limits

[Service]
# Limit memory to 2GB
MemoryMax=2G
MemoryHigh=1.8G

CPU limits

[Service]
# Limit to 50% of one CPU core
CPUQuota=50%

File descriptor limits

[Service]
# Increase file descriptor limit
LimitNOFILE=65536

Logging

View logs

# Follow logs in real-time
sudo journalctl -u workerd.service -f

# View recent logs
sudo journalctl -u workerd.service -n 100

# Filter by time
sudo journalctl -u workerd.service --since "1 hour ago"

Structured logging

workerd outputs to stderr. Configure journald to capture:
[Service]
StandardOutput=journal
StandardError=journal

Log rotation

Configure /etc/systemd/journald.conf:
[Journal]
SystemMaxUse=500M
SystemMaxFileSize=100M
SystemMaxFiles=5

Health checks

Systemd health monitoring

Add a health check script:
#!/bin/bash
# /usr/local/bin/workerd-health-check

curl -f http://localhost/health || exit 1
Configure in service file:
[Service]
ExecStartPost=/usr/local/bin/workerd-health-check

Restart on failure

[Service]
Restart=on-failure
RestartSec=5s
StartLimitBurst=3
StartLimitInterval=60s

Multiple instances

workerd is single-threaded. To utilize multiple cores, run multiple instances:

Service template

Create /etc/systemd/system/[email protected]:
[Unit]
Description=workerd runtime instance %i
After=local-fs.target remote-fs.target network-online.target
PartOf=workerd.target

[Service]
Type=exec
ExecStart=/usr/bin/workerd serve /etc/workerd/config.capnp --socket-fd http=3
Sockets=workerd@%i.socket
Restart=always
User=workerd
Group=workerd
NoNewPrivileges=true

[Install]
WantedBy=workerd.target

Socket template

Create /etc/systemd/system/[email protected]:
[Unit]
Description=workerd socket instance %i
PartOf=workerd@%i.service

[Socket]
ListenStream=127.0.0.1:808%i
ReusePort=true

[Install]
WantedBy=sockets.target

Target file

Create /etc/systemd/system/workerd.target:
[Unit]
Description=workerd instances
Wants[email protected] [email protected] [email protected] [email protected]

[Install]
WantedBy=multi-user.target

Start instances

# Start 4 instances on ports 8080, 8081, 8082, 8083
sudo systemctl enable workerd.target
sudo systemctl start workerd.target

# Check all instances
sudo systemctl status 'workerd@*'

Load balancer

Use nginx or haproxy to distribute load:
upstream workerd_backend {
    server 127.0.0.1:8080;
    server 127.0.0.1:8081;
    server 127.0.0.1:8082;
    server 127.0.0.1:8083;
}

server {
    listen 80;
    location / {
        proxy_pass http://workerd_backend;
    }
}

Updates and rollbacks

Zero-downtime updates

# Update workerd binary
sudo cp workerd-new /usr/bin/workerd

# Reload without dropping connections (if supported)
sudo systemctl reload-or-restart workerd.service

Rollback

# Restore previous binary
sudo cp /usr/bin/workerd.backup /usr/bin/workerd
sudo systemctl restart workerd.service

Monitoring

Metrics collection

workerd doesn’t include built-in metrics. Collect metrics from:
  • Application logs: Parse workerd’s stderr output
  • System metrics: CPU, memory, network via systemd
  • Health endpoint: Expose metrics in your worker

Example health endpoint

export default {
  async fetch(request) {
    if (new URL(request.url).pathname === "/health") {
      return new Response("OK", { status: 200 });
    }
    // Handle other requests
  }
};

Troubleshooting

Service won’t start

# Check service status
sudo systemctl status workerd.service

# View detailed logs
sudo journalctl -xeu workerd.service

# Test config manually
sudo -u workerd /usr/bin/workerd serve /etc/workerd/config.capnp

Socket activation issues

# Verify socket is listening
sudo systemctl status workerd.socket
sudo ss -tlnp | grep workerd

# Test socket activation
curl http://localhost:80

Permission denied

# Check file ownership
ls -la /etc/workerd/

# Fix permissions
sudo chown -R workerd:workerd /etc/workerd

Best practices

  1. Use socket activation: Avoid running workerd as root
  2. Enable security hardening: Use all available systemd security features
  3. Run multiple instances: One per CPU core for maximum throughput
  4. Monitor actively: Set up health checks and alerting
  5. Plan for updates: Test configuration changes before deploying
  6. Backup configs: Version control your workerd configurations
  7. Rotate logs: Configure appropriate journald retention

Reference

Build docs developers (and LLMs) love