Deploy production-ready Deno web applications with automatic process management using systemd and secure SSL termination through Nginx reverse proxy configuration.
Prerequisites
- Root or sudo access
- Domain name pointing to your server
- Basic knowledge of command line
What this solves
Deno is a modern JavaScript and TypeScript runtime that provides built-in security, dependency management, and web server capabilities. This tutorial shows you how to install Deno, create a web application, configure systemd for automatic process management, and set up Nginx as a reverse proxy with SSL termination for production deployment.
Step-by-step installation
Update system packages
Start by updating your package manager to ensure you have the latest security patches and dependencies.
sudo apt update && sudo apt upgrade -y
Install required dependencies
Install curl and unzip which are needed for the Deno installation script and extracting archives.
sudo apt install -y curl unzip
Install Deno runtime
Download and install the latest Deno binary using the official installation script. This installs Deno to your home directory and adds it to your PATH.
curl -fsSL https://deno.land/x/install/install.sh | sh
Add Deno to your PATH by updating your shell configuration.
echo 'export DENO_INSTALL="$HOME/.deno"' >> ~/.bashrc
echo 'export PATH="$DENO_INSTALL/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
Verify Deno installation
Check that Deno is properly installed and accessible from your PATH.
deno --version
You should see output showing the Deno version, V8 engine version, and TypeScript version.
Create application directory
Set up a dedicated directory structure for your Deno web application with proper permissions.
sudo mkdir -p /opt/deno-app
sudo chown $USER:$USER /opt/deno-app
cd /opt/deno-app
Create sample web application
Build a simple HTTP server using Deno's built-in server capabilities. This example demonstrates TypeScript support and modern JavaScript features.
import { serve } from "https://deno.land/std@0.208.0/http/server.ts";
const port = parseInt(Deno.env.get("PORT") || "8000");
const hostname = Deno.env.get("HOSTNAME") || "127.0.0.1";
const handler = (request: Request): Response => {
const url = new URL(request.url);
// Basic routing
if (url.pathname === "/") {
return new Response(JSON.stringify({
message: "Hello from Deno!",
timestamp: new Date().toISOString(),
version: Deno.version.deno
}), {
headers: { "content-type": "application/json" },
});
}
if (url.pathname === "/health") {
return new Response(JSON.stringify({ status: "healthy" }), {
headers: { "content-type": "application/json" },
});
}
return new Response("Not Found", { status: 404 });
};
console.log(HTTP server running on http://${hostname}:${port});
await serve(handler, { hostname, port });
Test the application
Run the Deno application to verify it works correctly before setting up systemd.
deno run --allow-net --allow-env server.ts
Test the server in another terminal to confirm it responds correctly.
curl http://127.0.0.1:8000
curl http://127.0.0.1:8000/health
Stop the test server with Ctrl+C before proceeding to systemd configuration.
Create dedicated user for application
Create a system user for running the Deno application securely without shell access.
sudo useradd --system --shell /usr/sbin/nologin --home /opt/deno-app --create-home deno-app
sudo chown -R deno-app:deno-app /opt/deno-app
Install Deno system-wide
Copy the Deno binary to a system location so the deno-app user can access it.
sudo cp ~/.deno/bin/deno /usr/local/bin/
sudo chmod 755 /usr/local/bin/deno
Create systemd service file
Configure systemd to manage your Deno application with automatic restarts and proper resource limits.
[Unit]
Description=Deno Web Application
After=network.target
Wants=network.target
[Service]
Type=simple
User=deno-app
Group=deno-app
WorkingDirectory=/opt/deno-app
ExecStart=/usr/local/bin/deno run --allow-net --allow-env server.ts
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
Security settings
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/deno-app
Resource limits
LimitNOFILE=65536
LimitNPROC=4096
Environment
Environment=PORT=8000
Environment=HOSTNAME=127.0.0.1
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.target
Enable and start the systemd service
Reload systemd configuration and start your Deno application service with automatic startup on boot.
sudo systemctl daemon-reload
sudo systemctl enable deno-app
sudo systemctl start deno-app
Install and configure Nginx
Set up Nginx as a reverse proxy to handle SSL termination and serve your Deno application securely.
sudo apt install -y nginx certbot python3-certbot-nginx
Configure Nginx virtual host
Create a virtual host configuration that proxies requests to your Deno application with security headers and caching.
server {
listen 80;
server_name example.com www.example.com;
# Redirect HTTP to HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com www.example.com;
# SSL configuration (certificates will be added by certbot)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# Security headers
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Gzip compression
gzip on;
gzip_types text/plain application/json application/javascript text/css;
# Proxy to Deno application
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 300s;
proxy_connect_timeout 75s;
}
# Health check endpoint caching
location /health {
proxy_pass http://127.0.0.1:8000;
proxy_cache_valid 200 1m;
add_header X-Cache-Status $upstream_cache_status;
}
}
Enable Nginx site and configure SSL
Enable the virtual host and obtain SSL certificates using Let's Encrypt. Replace example.com with your actual domain name.
sudo ln -s /etc/nginx/sites-available/deno-app /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl enable nginx
sudo systemctl start nginx
Obtain SSL certificates for your domain (replace example.com with your actual domain).
sudo certbot --nginx -d example.com -d www.example.com
Configure firewall
Open the necessary ports for web traffic while maintaining security. You can learn more about comprehensive firewall configuration in our Linux firewall tutorial.
sudo ufw allow 'Nginx Full'
sudo ufw --force enable
Verify your setup
Test that all components are working correctly together.
sudo systemctl status deno-app
sudo systemctl status nginx
curl -I https://example.com
curl https://example.com/health
Check the application logs to ensure everything is running smoothly.
sudo journalctl -u deno-app -f --lines=20
Verify SSL certificate configuration and security headers.
curl -I https://example.com
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Deno service fails to start | Permission denied accessing files | sudo chown -R deno-app:deno-app /opt/deno-app |
| 502 Bad Gateway error | Deno application not running | sudo systemctl restart deno-app |
| SSL certificate issues | Domain not pointing to server | Check DNS records and run sudo certbot renew --dry-run |
| Module download fails | Network permissions restricted | Add --allow-net flag to ExecStart in systemd service |
| High memory usage | No resource limits set | Add MemoryMax=512M to systemd service [Service] section |
Next steps
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Default values
DOMAIN="${1:-localhost}"
APP_USER="deno-app"
APP_DIR="/opt/deno-app"
SERVICE_NAME="deno-app"
# Usage
usage() {
echo "Usage: $0 [domain]"
echo "Example: $0 example.com"
exit 1
}
# Error handling
cleanup() {
echo -e "${RED}[ERROR] Installation failed. Cleaning up...${NC}"
systemctl stop "$SERVICE_NAME" 2>/dev/null || true
systemctl disable "$SERVICE_NAME" 2>/dev/null || true
rm -f "/etc/systemd/system/$SERVICE_NAME.service"
userdel "$APP_USER" 2>/dev/null || true
rm -rf "$APP_DIR"
rm -f /usr/local/bin/deno
}
trap cleanup ERR
log() {
echo -e "${GREEN}$1${NC}"
}
warn() {
echo -e "${YELLOW}$1${NC}"
}
error() {
echo -e "${RED}$1${NC}"
exit 1
}
# Check if running as root or with sudo
if [[ $EUID -ne 0 ]]; then
error "This script must be run as root or with sudo"
fi
# Detect OS
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update && apt upgrade -y"
PKG_INSTALL="apt install -y"
NGINX_CONF_DIR="/etc/nginx/sites-available"
NGINX_ENABLED_DIR="/etc/nginx/sites-enabled"
FIREWALL_CMD="ufw"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
NGINX_CONF_DIR="/etc/nginx/conf.d"
NGINX_ENABLED_DIR=""
FIREWALL_CMD="firewall-cmd"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
NGINX_CONF_DIR="/etc/nginx/conf.d"
NGINX_ENABLED_DIR=""
FIREWALL_CMD="firewall-cmd"
;;
*)
error "Unsupported distribution: $ID"
;;
esac
else
error "Cannot detect OS distribution"
fi
log "[1/12] Updating system packages..."
$PKG_UPDATE
log "[2/12] Installing required dependencies..."
$PKG_INSTALL curl unzip nginx
log "[3/12] Installing Deno runtime..."
# Install Deno to temporary location first
export DENO_INSTALL="/tmp/deno_install"
curl -fsSL https://deno.land/x/install/install.sh | sh
# Copy to system location
cp "$DENO_INSTALL/bin/deno" /usr/local/bin/
chmod 755 /usr/local/bin/deno
rm -rf "$DENO_INSTALL"
log "[4/12] Verifying Deno installation..."
/usr/local/bin/deno --version
log "[5/12] Creating application user..."
if ! id "$APP_USER" &>/dev/null; then
useradd --system --shell /usr/sbin/nologin --home "$APP_DIR" --create-home "$APP_USER"
fi
log "[6/12] Setting up application directory..."
mkdir -p "$APP_DIR"
chown -R "$APP_USER:$APP_USER" "$APP_DIR"
chmod 755 "$APP_DIR"
log "[7/12] Creating Deno web application..."
cat > "$APP_DIR/server.ts" << 'EOF'
import { serve } from "https://deno.land/std@0.208.0/http/server.ts";
const port = parseInt(Deno.env.get("PORT") || "8000");
const hostname = Deno.env.get("HOSTNAME") || "127.0.0.1";
const handler = (request: Request): Response => {
const url = new URL(request.url);
if (url.pathname === "/") {
return new Response(JSON.stringify({
message: "Hello from Deno!",
timestamp: new Date().toISOString(),
version: Deno.version.deno
}), {
headers: { "content-type": "application/json" },
});
}
if (url.pathname === "/health") {
return new Response(JSON.stringify({ status: "healthy" }), {
headers: { "content-type": "application/json" },
});
}
return new Response("Not Found", { status: 404 });
};
console.log(`HTTP server running on http://${hostname}:${port}`);
await serve(handler, { hostname, port });
EOF
chown "$APP_USER:$APP_USER" "$APP_DIR/server.ts"
chmod 644 "$APP_DIR/server.ts"
log "[8/12] Creating systemd service..."
cat > "/etc/systemd/system/$SERVICE_NAME.service" << EOF
[Unit]
Description=Deno Web Application
After=network.target
Wants=network.target
[Service]
Type=simple
User=$APP_USER
Group=$APP_USER
WorkingDirectory=$APP_DIR
ExecStart=/usr/local/bin/deno run --allow-net --allow-env server.ts
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
Environment=PORT=8000
Environment=HOSTNAME=127.0.0.1
[Install]
WantedBy=multi-user.target
EOF
chmod 644 "/etc/systemd/system/$SERVICE_NAME.service"
log "[9/12] Starting and enabling Deno service..."
systemctl daemon-reload
systemctl enable "$SERVICE_NAME"
systemctl start "$SERVICE_NAME"
log "[10/12] Configuring Nginx reverse proxy..."
if [ "$NGINX_ENABLED_DIR" ]; then
# Debian/Ubuntu style
NGINX_CONF="$NGINX_CONF_DIR/$DOMAIN"
cat > "$NGINX_CONF" << EOF
server {
listen 80;
server_name $DOMAIN;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_cache_bypass \$http_upgrade;
}
}
EOF
ln -sf "$NGINX_CONF" "$NGINX_ENABLED_DIR/"
else
# RHEL/CentOS style
cat > "$NGINX_CONF_DIR/$DOMAIN.conf" << EOF
server {
listen 80;
server_name $DOMAIN;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_cache_bypass \$http_upgrade;
}
}
EOF
fi
# Test and start Nginx
nginx -t
systemctl enable nginx
systemctl restart nginx
log "[11/12] Configuring firewall..."
case "$FIREWALL_CMD" in
"ufw")
ufw --force enable
ufw allow 'Nginx Full'
ufw allow ssh
;;
"firewall-cmd")
systemctl enable firewalld
systemctl start firewalld
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload
;;
esac
log "[12/12] Verifying installation..."
sleep 5
if systemctl is-active --quiet "$SERVICE_NAME"; then
log "✓ Deno service is running"
else
error "✗ Deno service is not running"
fi
if systemctl is-active --quiet nginx; then
log "✓ Nginx is running"
else
error "✗ Nginx is not running"
fi
# Test application
if curl -f -s http://127.0.0.1:8000/health > /dev/null; then
log "✓ Deno application is responding"
else
error "✗ Deno application is not responding"
fi
trap - ERR
log ""
log "🎉 Installation completed successfully!"
log ""
log "Your Deno application is now running at:"
log " Local: http://127.0.0.1:8000"
log " Domain: http://$DOMAIN"
log ""
log "Service management commands:"
log " sudo systemctl status $SERVICE_NAME"
log " sudo systemctl restart $SERVICE_NAME"
log " sudo journalctl -f -u $SERVICE_NAME"
log ""
warn "Next steps:"
warn "1. Configure your DNS to point $DOMAIN to this server"
warn "2. Install SSL certificate (recommend using certbot)"
warn "3. Update your application code in $APP_DIR/server.ts"
Review the script before running. Execute with: bash install.sh