Run Deno in Production with systemd

Khimananda Oli 8 min read Programming and Languages
Run Deno in Production with systemd

By Khimananda Oli | Last reviewed: August 2026

You have built your application and now need to run Deno in production with systemd so it survives reboots, captures logs, and restarts on failure without manual intervention. Unlike Node.js, Deno’s security-first permission model means your service unit must explicitly grant network, file, and environment access or the process will exit immediately. This guide provides a battle-tested systemd configuration that integrates with standard Linux observability tools like those covered in my systemd services and timers guide.

systemdPID 1 / ManagerDeno Process--allow-net --allow-readApp Files/opt/deno-appjournaldStructured Logs
Systemd manages the Deno process lifecycle while journald captures stdout/stderr securely without custom log files.

How do you configure a systemd unit to run Deno in production safely?

The foundation of any production runtime is isolation. Never run Deno as root. Create a dedicated system account with no login shell and a fixed home directory. This limits blast radius if the application is compromised and satisfies basic compliance controls for SOC 2 and ISO 27001 audits.

sudo useradd --system --no-create-home --shell /usr/sbin/nologin deno-app
sudo mkdir -p /opt/deno-app
sudo chown deno-app:deno-app /opt/deno-app

Next, write the unit file at /etc/systemd/system/deno-app.service. The configuration below reflects patterns I use across client environments in Nepal and globally where security and reliability are non-negotiable.

[Unit]
Description=Deno Production Application
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=deno-app
Group=deno-app
WorkingDirectory=/opt/deno-app
EnvironmentFile=/etc/deno-app/env
ExecStart=/usr/local/bin/deno run --allow-net=0.0.0.0:8000 --allow-read=/opt/deno-app/data --allow-env=DB_URL,API_KEY main.ts
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=deno-app
ProtectSystem=strict
ProtectHome=true
NoNewPrivileges=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target

Key directives deserve explanation. Type=simple is correct because Deno does not fork; it runs in the foreground. Restart=on-failure avoids infinite restart loops on configuration errors while recovering from transient crashes. ProtectSystem=strict makes the entire filesystem read-only except for explicit paths, preventing accidental writes outside your data directory. If your app needs to write elsewhere, add ReadWritePaths=/opt/deno-app/data.

Why explicit permissions matter more than wildcard flags

A common mistake is using --allow-all or broad --allow-read=/ flags for convenience. This defeats Deno’s security model and creates audit findings. Always scope permissions to the minimum required. If your app reads configuration from /etc/deno-app/config.json, grant only that path. Network permissions should specify host:port when possible rather than allowing all outbound traffic.

How do you manage secrets and environment variables for Deno services?

Never hardcode database credentials or API keys in your TypeScript source or unit file. Use an environment file owned by root with restricted permissions. This separates code from configuration and allows rotation without redeploying.

sudo mkdir -p /etc/deno-app
sudo nano /etc/deno-app/env

Add your variables in KEY=VALUE format, one per line:

DB_URL=postgres://user:pass@localhost:5432/appdb
API_KEY=sk-prod-xxxxx
LOG_LEVEL=info

Secure the file so only root can read it. The deno-app user accesses these variables through systemd’s EnvironmentFile directive, not direct file reads.

sudo chmod 600 /etc/deno-app/env
sudo chown root:root /etc/deno-app/env

In your Deno application, access these via Deno.env.get("DB_URL"). Remember to include --allow-env=DB_URL,API_KEY,LOG_LEVEL in your ExecStart command. Listing specific variable names is safer than --allow-env without arguments, which exposes all environment variables including potentially sensitive system ones.

For teams managing multiple environments, consider integrating with HashiCorp Vault or AWS Secrets Manager. My article on secrets management with Hashiorp Vault covers dynamic credential generation that pairs well with systemd-based deployments.

systemctl startLoad EnvFileDrop PrivilegesExec DenoValidate --allow FlagsBind Port / Read Files
Startup sequence validates permissions before binding resources, failing fast on misconfiguration rather than running insecurely.

How do you monitor and debug a Deno systemd service in production?

Systemd integrates natively with journald, eliminating the need for custom log files or external log shippers for basic observability. All stdout and stderr from your Deno process are captured with metadata including timestamps, PID, and unit name.

# View live logs
sudo journalctl -u deno-app.service -f

# View logs since last boot
sudo journalctl -u deno-app.service -b

# Filter by priority (errors only)
sudo journalctl -u deno-app.service -p err

For structured logging, configure Deno to output JSON. Most frameworks support this natively. Journald preserves JSON structure, making it queryable with journalctl --output=json or forwardable to centralized systems. If you are building a broader observability stack, my comparison of metrics, logs, and traces explains when to escalate beyond local journaling.

Health checks and graceful shutdowns

Deno supports signal handling for graceful shutdowns. Systemd sends SIGTERM by default, which Deno translates to a beforeunload-like event. Ensure your application closes database connections and finishes in-flight requests within the timeout window.

// In your Deno app
Deno.addSignalListener("SIGTERM", () => {
  console.log("Received SIGTERM, shutting down gracefully");
  server.close();
  Deno.exit(0);
});

Add TimeoutStopSec=30 to your unit file to give the application time to drain. If it exceeds this, systemd sends SIGKILL. For HTTP services, implement a /health endpoint and pair it with external monitoring. Tools like Prometheus can scrape this endpoint; see my Prometheus metrics monitoring fundamentals guide for integration patterns.

How does running Deno with systemd compare to Docker or PM2?

Choosing a runtime manager depends on your operational context. Systemd is ideal for bare-metal VPS, single-server deployments, and environments where container overhead is unjustified. It is also the native init system on Ubuntu, RHEL, and most Linux distributions, meaning zero additional dependencies.

CriteriasystemdDocker / ContainerPM2 / Node Supervisor
Boot IntegrationNative, starts before networking optionalRequires container runtime serviceUser-space, needs own startup hook
Resource OverheadNegligibleContainer runtime + image layersNode.js supervisor process
Security IsolationUser namespaces, ProtectSystem, NoNewPrivilegesKernel namespaces, cgroups, seccompProcess-level only
Log ManagementJournald, binary, indexedJSON-file or external driverCustom log files, rotation needed
Deno Permission ModelExplicit CLI flags in unitSame flags, plus container boundariesSame flags, no OS-level sandbox
Best ForVPS, edge, compliance-audited serversMicroservices, CI parity, multi-cloudLegacy Node apps, quick prototyping

In practice, I recommend systemd for Deno when you own the host and want minimal abstraction. Use containers when you need reproducible builds across teams or are deploying to Kubernetes. Avoid PM2 for Deno; it was designed for Node.js and adds complexity without benefiting from Deno’s built-in security model.

systemdLow OverheadNative SecurityJournald IntegratedBest: VPS / EdgeDockerMedium OverheadStrong IsolationExternal LoggingBest: K8s / TeamsPM2Higher OverheadWeak IsolationCustom Log FilesBest: Legacy Node
Tradeoff matrix for choosing a Deno runtime manager based on isolation needs and operational complexity.

What are common pitfalls when deploying Deno as a systemd service?

Even experienced engineers stumble on Deno-specific quirks. Here are issues I have diagnosed repeatedly in production:

  • Missing network permissions: Deno exits silently if --allow-net is omitted. Always check journalctl -u deno-app -n 50 after first start.
  • Wrong binary path: /usr/local/bin/deno is standard for manual installs, but package managers may place it elsewhere. Verify with which deno.
  • EnvironmentFile syntax errors: No spaces around =, no quotes unless part of the value. Invalid lines cause silent failures.
  • WorkingDirectory not owned by service user: Results in permission denied before Deno even starts. Always chown after creation.
  • Ignoring ProtectSystem implications: If your app writes to /tmp, add PrivateTmp=true or ReadWritePaths. Strict mode blocks all writes by design.

Test your unit thoroughly before enabling. Use systemd-analyze verify /etc/systemd/system/deno-app.service to catch syntax errors. Run sudo systemctl daemon-reload after every edit. Start manually with sudo systemctl start deno-app and watch logs in real-time before enabling at boot.

Run Deno in Production with systemd Reliably

Running Deno in production with systemd gives you native Linux integration, strong security defaults, and zero-dependency operations. The key is respecting Deno’s permission model within the unit file and leveraging systemd’s hardening directives. Start with the template above, scope permissions tightly, and validate with journalctl before going live. If you need help designing a production-grade Deno deployment or auditing your existing setup, reach out through my contact page for infrastructure consulting tailored to your environment.

Frequently Asked Questions

Create /etc/systemd/system/deno-app.service with Unit, Service, and Install sections. Set ExecStart to the full deno run path and your script. Enable with systemctl enable --now deno-app.service to start immediately and persist across reboots in 2026 production environments.

Never use root. Create a dedicated system user like deno-user with no login shell. Configure User= and Group= in the service file to limit filesystem access and reduce attack surface if the application is compromised during runtime.

Use EnvironmentFile=/etc/deno-app/env instead of inline Environment directives. Set file permissions to 600 owned by root. This keeps secrets out of process listings and unit files while allowing systemd to inject them before execution starts.

Specify only needed flags like --allow-net=8080 or --allow-read=/app/data. Avoid --allow-all in production systemd units. Explicit permissions enforce least privilege and prevent unauthorized filesystem or network access if code contains vulnerabilities.

Set Restart=on-failure and RestartSec=5 in the Service section. Add StartLimitIntervalSec=60 and StartLimitBurst=3 to prevent restart loops. Systemd will then automatically recover transient errors while stopping after repeated failures to avoid resource exhaustion.

No. Watch mode is for development only. Compile your TypeScript to a single binary using deno compile or rely on systemd restart policies for production reliability. Watch mode consumes excess resources and lacks deterministic behavior required for stable deployments.

Run journalctl -u deno-app.service -f to stream stdout and stderr in real time. Use --since "1 hour ago" for historical entries. Deno output integrates natively with journald without additional logging drivers or external log shippers.

Only if your app calls sd_notify. Most Deno servers work fine with Type=simple. Use Type=notify only when you implement explicit readiness signaling via the sd-notify crate to coordinate dependent services during startup sequences.

Add ProtectSystem=strict and ReadWritePaths=/app/writable in the service file. This uses Linux namespaces to make the entire filesystem read-only except specified paths, providing kernel-level isolation independent of Deno permission flags.

Deno does not support hot config reloads natively. Use ExecReload=/bin/kill -HUP $MAINPID only if your app handles SIGHUP. Otherwise, perform rolling restarts via systemctl restart deno-app.service during maintenance windows to apply configuration changes safely.

Add MemoryMax=512M to the Service section. Systemd enforces this via cgroups v2, killing the process if exceeded. Monitor usage with systemctl status deno-app.service to right-size limits based on actual production workload patterns.

Check file ownership matches the User directive. Verify SELinux or AppArmor profiles allow execution. Run journalctl -xeu deno-app.service for specific denials. Ensure the deno binary and script paths are readable by the service user account.

Prefer deno compile for production. Compiled binaries eliminate runtime TypeScript compilation overhead and reduce cold start latency. Update the ExecStart path to the compiled binary and remove --allow flags since permissions are baked into the executable at build time.

Set TimeoutStopSec=30 and ensure your Deno app listens for SIGTERM. Implement signal handlers to close active connections before exiting. Systemd sends SIGTERM first, then SIGKILL after timeout, preventing request drops during deployments or restarts.

Yes. Systemd is included in all major Linux distributions at no cost. Deno itself is open source. You pay only for underlying compute resources. There are no licensing fees for using systemd to manage Deno workloads in 2026.