Skip to content

Deploying Multiple Node.js Projects Under One Domain with Caddy

Introduction

In this guide, I’ll show you how to manage multiple Node.js applications under a single domain, each served from different subfolders, using Caddy. Whether you’re running a main application alongside a blog or managing multiple microservices, Caddy simplifies routing traffic to different Node.js applications. We’ll walk through deploying two Node.js projects under project.com and project.com/blog.

Prerequisites

Before starting, ensure you have: - A registered domain (project.com in our example) - A Linux server (Ubuntu/Debian recommended), see our list with recommended cloud providers - Node.js (v16 or higher) installed - PM2 process manager installed (npm install -g pm2) - Caddy 2.x installed - Basic command line knowledge - Root or sudo access to your server

Project Structure

First, let's set up our project structure:

/var/www/
├── main-app/
│   ├── package.json
│   ├── node_modules/
│   ├── src/
│   │   └── index.js
│   └── ...
└── blog/
    ├── package.json
    ├── node_modules/
    ├── src/
    │   └── index.js
    └── ...

Setting Up the Node.js Applications

Main Application Setup

// /var/www/main-app/src/index.js
const express = require('express');
const app = express();

app.get('/', (req, res) => {
    res.send('Welcome to the main application!');
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
    console.log(`Main app running on port ${PORT}`);
});

Blog Application Setup

// /var/www/blog/src/index.js
const express = require('express');
const app = express();

app.get('/', (req, res) => {
    res.send('Welcome to the blog!');
});

const PORT = process.env.PORT || 3001;
app.listen(PORT, () => {
    console.log(`Blog running on port ${PORT}`);
});

Installing Dependencies

# Main application
cd /var/www/main-app
npm init -y
npm install express

# Blog application
cd /var/www/blog
npm init -y
npm install express

Process Management with PM2

We'll use PM2 to manage our Node.js processes:

# Start main application
cd /var/www/main-app
pm2 start src/index.js --name "main-app"

# Start blog application
cd /var/www/blog
pm2 start src/index.js --name "blog-app"

# Save PM2 configuration
pm2 save

# Set up PM2 to start on system boot
pm2 startup

Caddy Configuration

Create or edit your Caddyfile at /etc/caddy/Caddyfile:

project.com {
    # Global settings
    tls admin@project.com
    encode gzip

    # Main application
    handle /* {
        reverse_proxy localhost:3000
    }

    # Blog application
    handle /blog/* {
        uri strip_prefix /blog
        reverse_proxy localhost:3001
    }

    # Handle errors
    handle_errors {
        respond "{http.error.status_code} {http.error.status_text}"
    }
}

Let's break down the configuration:

  • Domain Configuration:
  • The block starts with project.com to handle all requests to this domain

  • Global Settings:

  • tls: Enables automatic HTTPS certification
  • encode gzip: Enables compression for better performance

  • Main Application Handler:

  • Handles all requests to the root domain
  • Proxies to the main application running on port 3000

  • Blog Handler:

  • Handles requests to /blog
  • Strips the /blog prefix before proxying
  • Proxies to the blog application running on port 3001

Security Configuration

Application Security

Add basic security middleware to both applications:

// Add to both Node.js applications
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');

// Install dependencies
npm install helmet express-rate-limit

// Configure security middleware
app.use(helmet());

const limiter = rateLimit({
    windowMs: 15 * 60 * 1000, // 15 minutes
    max: 100 // limit each IP to 100 requests per windowMs
});

app.use(limiter);

Caddy Security Headers

Add security headers in your Caddyfile:

project.com {
    header {
        # HSTS
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        # XSS Protection
        X-XSS-Protection "1; mode=block"
        # Content Security Policy
        Content-Security-Policy "default-src 'self'"
        # Frame Options
        X-Frame-Options "SAMEORIGIN"
        # Remove Server header
        -Server
    }

    # ... rest of your configuration
}

Deployment Steps

  • Prepare the Server:
# Update system
sudo apt update && sudo apt upgrade -y

# Create project directories
sudo mkdir -p /var/www/{main-app,blog}
sudo chown -R $USER:$USER /var/www
  • Deploy Applications:
# Deploy main app
cd /var/www/main-app
git clone [your-main-app-repo] .
npm install
npm run build # if applicable

# Deploy blog
cd /var/www/blog
git clone [your-blog-repo] .
npm install
npm run build # if applicable
  • Start Services:
# Restart PM2 processes
pm2 restart all

# Validate Caddy configuration
sudo caddy validate --config /etc/caddy/Caddyfile

# Restart Caddy
sudo systemctl restart caddy

Monitoring and Maintenance

Application Monitoring

# Monitor PM2 processes
pm2 monit

# View application logs
pm2 logs main-app
pm2 logs blog-app

# Check process status
pm2 status

Caddy Monitoring

# View Caddy logs
sudo journalctl -u caddy --follow

# Check Caddy status
sudo systemctl status caddy

Possible Issues

  • Connection Refused:
# Check if Node.js applications are running
pm2 status

# Check ports
sudo netstat -tulpn | grep LISTEN
  • 502 Bad Gateway:
# Check application logs
pm2 logs

# Verify Caddy configuration
sudo caddy validate --config /etc/caddy/Caddyfile
  • SSL Issues:
# Check Caddy SSL certificates
ls -la /var/lib/caddy/.local/share/caddy/certificates

# Verify DNS settings
dig project.com

Backup Strategy

  • Application Backups:
# Create backup script
cat > backup.sh << 'EOF'
#!/bin/bash
BACKUP_DIR="/backup/nodejs-apps"
DATE=$(date +%Y%m%d)
mkdir -p "$BACKUP_DIR"

# Backup applications
tar -czf "$BACKUP_DIR/main-app-$DATE.tar.gz" /var/www/main-app
tar -czf "$BACKUP_DIR/blog-$DATE.tar.gz" /var/www/blog

# Backup PM2 configuration
pm2 save
cp -r ~/.pm2 "$BACKUP_DIR/pm2-$DATE"

# Backup Caddy configuration
cp /etc/caddy/Caddyfile "$BACKUP_DIR/Caddyfile-$DATE"
EOF

chmod +x backup.sh