DEV Community

Alex Chen
Alex Chen

Posted on

How I Deploy Node.js Apps to Production (2026)

How I Deploy Node.js Apps to Production (2026)

My exact deployment process. From code to live in minutes.

The Stack

Server: Ubuntu on DigitalOcean/Linode/VPS ($5-10/month)
Runtime: Node.js 22 LTS
Process Manager: systemd (built-in, no PM2 needed)
Reverse Proxy: Nginx
SSL: Let's Encrypt (free, auto-renew)
CI/CD: GitHub Actions (free for public repos)
Enter fullscreen mode Exit fullscreen mode

1. Server Setup (One-Time)

# Update system
sudo apt update && sudo apt upgrade -y

# Install Node.js (via NodeSource)
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs

# Install Nginx
sudo apt install -y nginx

# Install Certbot (for SSL)
sudo apt install -y certbot python3-certbot-nginx

# Create app user (don't run as root!)
sudo useradd -m -s /bin/bash appuser
sudo usermod -aG sudo appuser

# Firewall
sudo ufw allow 22    # SSH
sudo ufw allow 80    # HTTP
sudo ufw allow 443   # HTTPS
sudo ufw enable
Enter fullscreen mode Exit fullscreen mode

2. Deploy the App

# Create app directory
sudo mkdir -p /var/www/myapp
sudo chown appuser:appuser /var/www/myapp

# Clone or copy code
cd /var/www/myapp
git clone https://github.com/you/your-app.git .

# Install dependencies
npm ci --production

# Build (if needed)
npm run build

# Create .env file
cat > .env << 'EOF'
NODE_ENV=production
PORT=3000
DATABASE_URL=postgres://...
JWT_SECRET=your-secret-here
EOF

chmod 600 .env  # Only owner can read
Enter fullscreen mode Exit fullscreen mode

3. systemd Service (Built-in Process Manager)

# Create service file
sudo nano /etc/systemd/system/myapp.service
Enter fullscreen mode Exit fullscreen mode
[Unit]
Description=My Node.js Application
After=network.target

[Service]
Type=simple
User=appuser
WorkingDirectory=/var/www/myapp
ExecStart=/usr/bin/node server.js
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=myapp

# Security hardening
NoNewPrivileges=true
ReadOnlyPaths=/var/www/myapp
PrivateTmp=true

[Install]
WantedBy=multi-user.target
Enter fullscreen mode Exit fullscreen mode
# Start and enable
sudo systemctl daemon-reload
sudo systemctl enable myapp    # Auto-start on boot
sudo systemctl start myapp

# Useful commands
sudo systemctl status myapp    # Check status
sudo systemctl restart myapp   # Restart
sudo systemctl stop myapp      # Stop
sudo journalctl -u myapp -f    # Follow logs
Enter fullscreen mode Exit fullscreen mode

4. Nginx Configuration

sudo nano /etc/nginx/sites-available/myapp
Enter fullscreen mode Exit fullscreen mode
server {
    listen 80;
    server_name example.com www.example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        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;

        # Timeouts
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    # Rate limiting zone (defined in nginx.conf)
    limit_req zone=api burst=20 nodelay;

    # Block sensitive files
    location ~ /\. {
        deny all;
    }
}
Enter fullscreen mode Exit fullscreen mode
# Enable site
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t                    # Test config
sudo systemctl reload nginx      # Apply

# SSL (Let's Encrypt)
sudo certbot --nginx -d example.com -d www.example.com
# Auto-renew: certbot adds a cron job automatically
Enter fullscreen mode Exit fullscreen mode

5. GitHub Actions CI/CD

# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Deploy to server
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: appuser
          key: ${{ secrets.SSH_KEY }}
          script: |
            cd /var/www/myapp
            git pull origin main
            npm ci --production
            npm run build
            sudo systemctl restart myapp
Enter fullscreen mode Exit fullscreen mode

6. Health Check Endpoint

// server.js
app.get('/health', (req, res) => {
  res.json({
    status: 'ok',
    uptime: process.uptime(),
    memory: process.memoryUsage(),
    timestamp: new Date().toISOString(),
  });
});
Enter fullscreen mode Exit fullscreen mode

7. Monitoring

# Log rotation (prevents disk filling)
sudo nano /etc/logrotate.d/myapp
Enter fullscreen mode Exit fullscreen mode
/var/log/myapp.log {
  daily
  rotate 7
  compress
  missingok
  notifempty
  copytruncate
}
Enter fullscreen mode Exit fullscreen mode
# Basic monitoring script (add to crontab)
*/5 * * * * curl -sf http://localhost:3000/health || systemctl restart myapp
Enter fullscreen mode Exit fullscreen mode

Quick Checklist

□ Server updated and secured
□ App runs as non-root user
□ systemd service configured (auto-restart + auto-start)
□ Nginx reverse proxy with security headers
□ SSL certificate (Let's Encrypt)
□ .env file with 600 permissions
□ .gitignore includes .env
□ Health check endpoint
□ Log rotation configured
□ CI/CD pipeline set up
□ Firewall (ufw) configured
□ Rate limiting on Nginx
Enter fullscreen mode Exit fullscreen mode

What's your deployment process? Any tools I'm missing?

Follow @armorbreak for more DevOps content.

Top comments (0)