
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
To reliably run Bun in production with systemd, you must create a dedicated service unit file that manages the process lifecycle, isolates resources, and integrates with the system journal. While Bun’s raw performance is impressive, running it directly in a terminal session lacks the resilience required for enterprise workloads. This guide provides the exact configuration patterns I use to deploy Bun applications on Ubuntu and RHEL systems, ensuring they survive reboots and recover from crashes automatically. For foundational server hardening before deploying any runtime, review my initial Ubuntu server setup guide.
How do you configure a systemd unit file to run Bun in production?
The core of running Bun in production with systemd is a well-crafted unit file. Unlike Node.js, Bun is often installed per-user or via a standalone installer, meaning its binary path is rarely in the default system PATH. You must specify the absolute path to the executable. Create the file at /etc/systemd/system/bun-app.service.
[Unit]
Description=Bun Production Application
Documentation=https://bun.sh/docs
After=network.target postgresql.service redis.service
Wants=postgresql.service redis.service
[Service]
Type=simple
User=bunuser
Group=bunuser
WorkingDirectory=/var/www/bun-app
EnvironmentFile=/etc/bun-app/env
ExecStart=/home/bunuser/.bun/bin/bun run src/index.ts
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=bun-app
# Security Hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=read-only
ReadWritePaths=/var/www/bun-app/data /var/log/bun-app
PrivateTmp=true
[Install]
WantedBy=multi-user.target Key directives explained
- Type=simple: Bun runs as a foreground process. Do not use
Type=forkingunless your application explicitly daemonizes itself, which is an anti-pattern in modern cloud-native deployments. - ExecStart: Use the full path. Find it with
which bunorreadlink -f $(which bun). If you installed Bun globally as root, this might be/usr/local/bin/bun; for user installs, it is typically under~/.bun/bin. - EnvironmentFile: Never hardcode secrets in the unit file. Point to a restricted file (mode 0600) owned by the service user. This separates configuration from infrastructure definition.
- Restart=always: Ensures the service restarts regardless of exit code. Pair with
RestartSec=5to prevent tight crash loops from saturating CPU during persistent failures.
What security hardening is required for Bun systemd services?
Running any web-facing runtime as root is unacceptable. When you run Bun in production with systemd, apply defense-in-depth principles consistent with SOC 2 and ISO 27001 controls. The unit file above includes several critical sandboxing directives that limit the blast radius if the application is compromised.
- Dedicated Service Account: Create a system user with no login shell:
useradd --system --no-create-home --shell /usr/sbin/nologin bunuser. This prevents interactive access even if credentials are leaked. - Filesystem Restrictions:
ProtectSystem=strictmounts the entire filesystem as read-only except for paths explicitly listed inReadWritePaths. This prevents an attacker from modifying system binaries or planting backdoors in/tmpor/etc. - Capability Dropping: Add
AmbientCapabilities=CAP_NET_BIND_SERVICEonly if Bun needs to bind to ports below 1024. Otherwise, let it bind to high ports and use a reverse proxy like Nginx or HAProxy for TLS termination and port forwarding. My Nginx installation guide covers this reverse proxy pattern in detail. - Network Isolation: For highly sensitive workloads, consider
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIXto block exotic socket types, or use systemd-networkd to apply network-level filtering per service.
A common mistake is granting excessive permissions to the WorkingDirectory. Ensure ownership is correct: chown -R bunuser:bunuser /var/www/bun-app. Avoid chmod 777 under any circumstances; use 750 for directories and 640 for config files.
How does Bun compare to Node.js when managed by systemd?
Many teams migrating to Bun ask whether their existing Node.js systemd configurations transfer directly. In practice, the unit structure is identical, but runtime behavior differs in ways that affect operational reliability. Understanding these differences prevents subtle production incidents.
| Criteria | Node.js (v22+) | Bun (v1.2+) |
|---|---|---|
| Binary Path Stability | Predictable (/usr/bin/node) | Varies by install method; verify with which |
| Startup Time | ~80–150ms for HTTP server | ~10–30ms for equivalent server |
| Memory Footprint | Higher baseline (~40–60MB) | Lower baseline (~20–35MB) |
| Native Module Compatibility | Mature ecosystem | Growing; some native addons require rebuild |
| TypeScript Execution | Requires ts-node or build step | Native .ts support in ExecStart |
| Signal Handling | Standard POSIX compliance | Generally compliant; test SIGTERM gracefully |
The most significant operational advantage when you run Bun in production with systemd is the native TypeScript execution. Your ExecStart command can point directly to .ts files without a compilation step or wrapper, simplifying the deployment artifact. However, always test signal handling: send SIGTERM manually (kill -15 $PID) to confirm your app shuts down cleanly before relying on systemd’s restart policies.
How do you manage logs and monitor a Bun systemd service?
Systemd captures stdout and stderr automatically when StandardOutput=journal is set. This eliminates the need for external log rotation scripts or PM2-style log managers. Query logs with journalctl:
# Live tail with structured output
journalctl -u bun-app.service -f -o json-pretty
# Filter by time range and priority
journalctl -u bun-app.service --since "2026-08-20 09:00:00" --priority=err
# Export for external aggregation
journalctl -u bun-app.service --output=json | jq '.MESSAGE' For production observability, integrate with your existing stack. If you use Prometheus, expose a /metrics endpoint in your Bun app and scrape it via a separate systemd timer or Prometheus agent. For centralized logging, ship journald entries to Graylog or Loki using systemd-journal-remote or a lightweight forwarder. My article on structured logging best practices details how to format Bun console output for machine parsing.
Set up alerting on the service state itself. A systemd watchdog or a simple cron job checking systemctl is-active bun-app.service provides a baseline availability signal independent of application-level metrics. Combine this with the four golden signals framework to distinguish between infrastructure failures and application degradation.
Run Bun in Production with systemd Reliably
Deploying Bun via systemd gives you zero-cost process supervision, integrated logging, and security sandboxing without additional dependencies. The key steps are verifying the absolute binary path, enforcing least-privilege through a dedicated user and filesystem restrictions, and validating signal handling before going live. Treat your unit file as infrastructure code: version it, review it, and test it in staging first. If you need help designing a production-grade deployment pipeline for Bun or auditing your existing systemd configurations for compliance, reach out to discuss your infrastructure.