
Table of Contents
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.
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.
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.
| Criteria | systemd | Docker / Container | PM2 / Node Supervisor |
|---|---|---|---|
| Boot Integration | Native, starts before networking optional | Requires container runtime service | User-space, needs own startup hook |
| Resource Overhead | Negligible | Container runtime + image layers | Node.js supervisor process |
| Security Isolation | User namespaces, ProtectSystem, NoNewPrivileges | Kernel namespaces, cgroups, seccomp | Process-level only |
| Log Management | Journald, binary, indexed | JSON-file or external driver | Custom log files, rotation needed |
| Deno Permission Model | Explicit CLI flags in unit | Same flags, plus container boundaries | Same flags, no OS-level sandbox |
| Best For | VPS, edge, compliance-audited servers | Microservices, CI parity, multi-cloud | Legacy 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.
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-netis omitted. Always checkjournalctl -u deno-app -n 50after first start. - Wrong binary path:
/usr/local/bin/denois standard for manual installs, but package managers may place it elsewhere. Verify withwhich 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
chownafter creation. - Ignoring ProtectSystem implications: If your app writes to
/tmp, addPrivateTmp=trueorReadWritePaths. 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.