
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
Deploying a compiled Go binary directly via SSH or screen sessions is a common anti-pattern that leads to silent failures and unmanageable uptime. To run Go in production with systemd, you must treat your application as a managed system service rather than an ad-hoc process. This approach integrates your application into the OS lifecycle, providing automatic restarts, structured logging via journald, and strict security sandboxing. For teams managing infrastructure on Ubuntu or RHEL-based systems, this is the foundational layer of reliability before you even consider orchestration tools like Kubernetes.
How do you configure a systemd unit file to run Go in production?
The unit file is the contract between your Go application and the Linux kernel. A common mistake I see in audits is running services as root or missing explicit dependency declarations. When you manage Linux daemons with systemd, precision in the unit file prevents cascading failures during boot or high-load events. Create your service definition at /etc/systemd/system/myapp.service. Never edit files in /lib/systemd/system/ as package updates will overwrite them.
[Unit]
Description=My Go Production API Service
Documentation=https://internal.docs/myapp
After=network.target postgresql.service
Wants=postgresql.service
[Service]
Type=simple
User=myapp
Group=myapp
ExecStart=/opt/myapp/bin/myapp -config /etc/myapp/config.yaml
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5s
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp
# Security Hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/myapp /var/log/myapp
PrivateTmp=true
MemoryDenyWriteExecute=true
[Install]
WantedBy=multi-user.target Critical directives explained
- Type=simple: Go binaries are long-running foreground processes by default. Do not use
Type=forkingunless your application explicitly daemonizes itself, which is rare and discouraged in modern Go development. - Restart=on-failure: Automatically restarts the process if it exits with a non-zero code or is killed by a signal. Avoid
alwaysin production as it can mask configuration errors by infinitely restarting a broken binary. - After=network.target: Ensures the network stack is up before starting. If your Go app depends on a specific database, add it here to prevent startup race conditions.
- SyslogIdentifier: Tags all log entries. Without this, filtering logs via
journalctlbecomes difficult when multiple services share similar output patterns.
What security hardening is required for Go services on Linux?
Running a web-facing Go application without sandboxing violates basic defense-in-depth principles. In my experience helping teams achieve SOC 2 compliance, auditors specifically look for least-privilege execution. Systemd provides kernel-level isolation that application code cannot bypass. Even if an attacker exploits a vulnerability in your Go HTTP handler, these directives limit the blast radius significantly.
| Directive | Purpose | Production Recommendation |
|---|---|---|
NoNewPrivileges=true | Prevents the process and children from gaining new privileges via setuid/setgid | Mandatory for all internet-facing services |
ProtectSystem=strict | Mounts entire filesystem read-only except whitelisted paths | Use with ReadWritePaths for data directories only |
PrivateTmp=true | Gives the service its own isolated /tmp directory | Prevents symlink attacks and temp file leakage |
MemoryDenyWriteExecute=true | Blocks creating memory mappings that are both writable and executable | Stops many buffer overflow and JIT spray exploits |
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX | Limits socket types the process can create | Block netlink/raw sockets unless explicitly needed |
Always create a dedicated system user for your Go application. Never run as root or a shared user like www-data. Use useradd --system --no-create-home --shell /usr/sbin/nologin myapp to create a non-login account. This ensures that even if the service is compromised, the attacker cannot easily pivot to other system resources or establish interactive shells.
How do you manage logs and observability for Go systemd services?
When you run Go in production with systemd, your application should write to stdout/stderr, not directly to files. Systemd captures this output and routes it to journald. This eliminates the need for application-level log rotation and integrates your Go service into the standard Linux observability stack. For teams already using structured logging best practices, this model works seamlessly with JSON parsers.
Essential journalctl commands
- Live tailing:
journalctl -u myapp.service -ffollows new log entries in real-time, equivalent totail -fbut with metadata enrichment. - Time-bounded queries:
journalctl -u myapp.service --since "2026-08-18 09:00:00" --until "2026-08-18 10:00:00"isolates incidents without grepping massive files. - Boot-specific logs:
journalctl -u myapp.service -bshows only entries from the current boot cycle, critical for diagnosing startup failures after reboots. - JSON export:
journalctl -u myapp.service -o json-prettyoutputs full metadata including PID, UID, and cgroup path for forensic analysis.
Configure journald persistence in /etc/systemd/journald.conf by setting Storage=persistent. By default, many distributions store logs in volatile memory, losing them on reboot. For production Go services, you need durable logs for post-incident review and compliance evidence. Set SystemMaxUse=2G to prevent disk exhaustion. If you ship logs to a centralized system like Loki or Elasticsearch, configure ForwardToSyslog=no to avoid double-writing and reduce I/O overhead.
How does systemd compare to Docker and Kubernetes for Go deployment?
Engineers often ask whether they should skip systemd entirely and containerize. The answer depends on operational complexity and scale. For single-node deployments, edge servers, or environments where container runtime overhead is unacceptable, systemd remains the gold standard. Understanding this trade-off prevents over-engineering. I have seen startups waste months building Kubernetes clusters for applications that would have been more reliable and cheaper as hardened systemd services on a pair of VPS instances.
| Criteria | systemd Direct | Docker Compose | Kubernetes |
|---|---|---|---|
| Startup Overhead | Near-zero (native binary) | Low (container runtime) | High (kubelet + control plane) |
| Security Isolation | cgroups + namespaces (manual) | Container namespaces (default) | Pod security + network policies |
| Auto-Restart | Restart=on-failure (instant) | restart: unless-stopped | Liveness probes + controller |
| Log Management | journald (integrated) | JSON file driver / plugin | Sidecar or node-level agent |
| Config Updates | systemctl reload (SIGHUP) | docker compose up -d | Rolling deployment / GitOps |
| Best For | Single node, edge, low-latency | Dev parity, small teams | Multi-node, auto-scaling, SaaS |
If you choose systemd, you gain direct access to kernel features without abstraction layers. Debugging becomes simpler because there is no container runtime between you and the process. However, you lose portability and declarative infrastructure. My recommendation: start with systemd for MVPs and internal tools. Migrate to containers only when you need horizontal scaling or multi-cloud portability. Both approaches require the same foundational discipline around Ubuntu security hardening and least-privilege execution.
Run Go in Production with systemd Reliably
Treating your Go application as a first-class systemd citizen pays dividends in stability, security, and operational clarity. Start with a hardened unit file, enforce least-privilege execution, and integrate with journald from day one. Resist the urge to containerize prematurely; native systemd services remain the most efficient way to deploy Go on Linux for small-to-medium workloads. When your architecture grows beyond single-node capacity, the discipline you build here translates directly to containerized environments. If you need help designing production-grade deployment pipelines or auditing existing Go services for compliance gaps, reach out to discuss your infrastructure.