Run Bun in Production with systemd

Khimananda Oli 7 min read Programming and Languages
Run Bun in Production with systemd

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.

systemd PID 1Service ManagerBun RuntimeApp ProcessjournaldStructured LogsExecStartstdout/stderrRestart=always
Systemd supervises the Bun process, capturing logs to journald and automatically restarting on failure.

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=forking unless your application explicitly daemonizes itself, which is an anti-pattern in modern cloud-native deployments.
  • ExecStart: Use the full path. Find it with which bun or readlink -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=5 to 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.

  1. 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.
  2. Filesystem Restrictions: ProtectSystem=strict mounts the entire filesystem as read-only except for paths explicitly listed in ReadWritePaths. This prevents an attacker from modifying system binaries or planting backdoors in /tmp or /etc.
  3. Capability Dropping: Add AmbientCapabilities=CAP_NET_BIND_SERVICE only 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.
  4. Network Isolation: For highly sensitive workloads, consider RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX to 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.

systemctl startLoad EnvironmentFileApply Sandbox & CapsExecStart BunActive & Monitored
Sequential startup flow: environment injection, sandbox enforcement, process execution, and active supervision.

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.

CriteriaNode.js (v22+)Bun (v1.2+)
Binary Path StabilityPredictable (/usr/bin/node)Varies by install method; verify with which
Startup Time~80–150ms for HTTP server~10–30ms for equivalent server
Memory FootprintHigher baseline (~40–60MB)Lower baseline (~20–35MB)
Native Module CompatibilityMature ecosystemGrowing; some native addons require rebuild
TypeScript ExecutionRequires ts-node or build stepNative .ts support in ExecStart
Signal HandlingStandard POSIX complianceGenerally 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.

Node.js Under systemdBaseline Memory: ~50 MBStartup: ~120msRequires Build Step for TSComplex ExecStartMature Native ModulesStable EcosystemBun Under systemdBaseline Memory: ~25 MBStartup: ~20msNative TS ExecutionDirect .ts in ExecStartGrowing Native SupportVerify Critical Addons
Resource and workflow comparison between Node.js and Bun when managed as systemd services in production.

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.

Frequently Asked Questions

Create /etc/systemd/system/bun-app.service with ExecStart pointing to the bun binary and your entry script. Set User, WorkingDirectory, and Restart=always. Run systemctl daemon-reload then enable and start the service to manage your Bun application in production reliably.

Systemd integrates natively with Linux init, offers faster boot times, and eliminates Node.js runtime overhead. It provides better resource control via cgroups, automatic restart policies, and journal logging without requiring an additional process manager dependency or extra memory footprint for running Bun applications.

Install Bun globally to /usr/local/bin/bun using the official installer. Avoid user-local paths like ~/.bun because systemd runs as root or a specific service user. Verify the absolute path with which bun before referencing it in your unit file configuration.

Use the Environment directive for simple values or EnvironmentFile for sensitive secrets in your service unit. Never hardcode credentials directly in the unit file. Point EnvironmentFile to a root-owned 0600 permission file containing KEY=value pairs loaded securely at service startup time.

Yes. Set KillMode=mixed and TimeoutStopSec=30 in your unit file. Bun handles SIGTERM automatically for HTTP servers. Implement signal handlers in your application code to close database connections and finish in-flight requests before the process exits cleanly during deployments.

Use journalctl -u bun-app.service -f to stream live output. Systemd captures stdout and stderr automatically. Configure StandardOutput=journal and StandardError=journal in your unit file. Add --since today or --no-pager flags for filtered viewing without external log files or rotation configuration.

Create a dedicated system user like bunapp with no login shell. Never run production services as root. Set User=bunapp and Group=bunapp in the unit file. Restrict file permissions so only this user can read application code and write to necessary directories.

Set Restart=on-failure and RestartSec=5 in your service unit. This restarts only after non-zero exit codes, preventing restart loops from configuration errors. Combine with StartLimitIntervalSec and StartLimitBurst to cap restart attempts within a time window and avoid system resource exhaustion.

Yes. Use MemoryMax=512M and CPUQuota=80% directives in the service unit. These cgroup v2 controls prevent runaway processes from starving other services. Monitor actual usage with systemctl status bun-app.service and adjust limits based on production load testing and observed peak consumption patterns.

Use ExecReload=/bin/kill -HUP $MAINPID if your app supports SIGHUP. Otherwise implement a blue-green deployment strategy with socket activation. Define ListenStream in a socket unit and let systemd pass file descriptors to new instances while draining old ones gracefully.

Yes. Bun reached 1.0 in late 2023 and has maintained API stability through 2026. Many teams run it in production with systemd. Pin a specific version rather than latest, test thoroughly in staging, and monitor error rates during initial rollout phases carefully.

Apply ProtectSystem=strict, ProtectHome=yes, NoNewPrivileges=true, and PrivateTmp=yes. Whitelist only required directories with ReadWritePaths. Drop all capabilities except those explicitly needed. These sandboxing directives minimize blast radius if your Bun application is compromised in production environments.

Ports below 1024 require CAP_NET_BIND_SERVICE capability or root privileges. Prefer ports above 1024 or grant the capability explicitly. Use AmbientCapabilities=CAP_NET_BIND_SERVICE in the unit file rather than running as root. Configure reverse proxy upstream to forward traffic safely.

Download the new Bun binary to a temporary location, verify checksums, then atomically replace /usr/local/bin/bun. Run systemctl restart bun-app.service afterward. Test in staging first. Keep the previous binary as backup until you confirm the new version works correctly under production load.

Yes but configure carefully. Let Bun handle internal clustering while systemd manages the parent process. Set Type=simple not forking. Do not use systemd slice-based parallelism alongside Bun clustering as they compete for resources. Monitor worker health through application metrics not just process state.