Skip to content

๐Ÿ”’ Reverse Proxy & SSL โ€‹

A reverse proxy sits between the internet and your KitchenAsty containers. It handles HTTPS encryption, routes requests to the correct service, and provides a professional setup with proper SSL certificates.

We cover two options: Caddy (easiest, recommended) and Nginx + Certbot (most common). Choose one.


Caddy is a modern web server that automatically obtains and renews SSL certificates from Let's Encrypt. It requires almost no configuration.

โœจ Why Caddy? โ€‹

  • ๐Ÿ”’ Automatic HTTPS โ€” no manual certificate setup or renewal cron jobs
  • ๐Ÿ“ Simple configuration โ€” a few lines vs. hundreds for Nginx
  • โšก HTTP/2 and HTTP/3 out of the box
  • ๐Ÿ”„ Automatic redirects โ€” HTTP to HTTPS happens automatically

๐Ÿ“ฆ Install Caddy โ€‹

On your server:

bash
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudflare.com/cloudflare/d/deb/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg 2>/dev/null || true
curl -1sLf 'https://dl.cloudflare.com/cloudflare/d/deb/config.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list > /dev/null 2>&1 || true

# Use the official Caddy install method
sudo apt install -y caddy 2>/dev/null || {
  # Fallback: install from official Caddy repo
  curl -1sLf 'https://dl.cloudflare.com/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
  curl -1sLf 'https://dl.cloudflare.com/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
  sudo apt update && sudo apt install -y caddy
}

Or use the simplest method:

bash
sudo apt update
sudo apt install -y caddy

๐Ÿ’ก

If apt install caddy isn't available on your distro, download the binary from caddyserver.com/download.

โš™๏ธ Configure Caddy โ€‹

Edit the Caddyfile:

bash
sudo nano /etc/caddy/Caddyfile

Replace the contents with:

caddyfile
# Admin Dashboard
admin.yourdomain.com {
    reverse_proxy kitchenasty-admin:80
}

# Customer Storefront
order.yourdomain.com {
    reverse_proxy kitchenasty-storefront:80
}

# API Server
api.yourdomain.com {
    reverse_proxy kitchenasty-server:3000
}

That's the entire configuration. Caddy will:

  1. ๐Ÿ”’ Automatically obtain SSL certificates from Let's Encrypt
  2. ๐Ÿ”„ Redirect HTTP to HTTPS
  3. โ™ป๏ธ Renew certificates before they expire
  4. โšก Enable HTTP/2

๐Ÿ”— Connect Caddy to Docker Network โ€‹

Caddy needs to reach the Docker containers. The easiest way is to add Caddy to the same Docker network:

bash
# Connect Caddy to the KitchenAsty Docker network
docker network connect kitchenasty caddy 2>/dev/null || true

However, since Caddy is installed as a system service (not a container), you need to expose the container ports on localhost instead. Update your docker-compose.prod.yml to expose ports on 127.0.0.1 only (not publicly):

yaml
  server:
    # ... existing config ...
    ports:
      - "127.0.0.1:3000:3000"

  admin:
    # ... existing config ...
    ports:
      - "127.0.0.1:5173:80"

  storefront:
    # ... existing config ...
    ports:
      - "127.0.0.1:5174:80"

Then update the Caddyfile to use localhost instead of container names:

caddyfile
# Admin Dashboard
admin.yourdomain.com {
    reverse_proxy localhost:5173
}

# Customer Storefront
order.yourdomain.com {
    reverse_proxy localhost:5174
}

# API Server โ€” includes WebSocket support
api.yourdomain.com {
    reverse_proxy localhost:3000
}

๐Ÿš€ Start Caddy โ€‹

bash
sudo systemctl enable caddy
sudo systemctl restart caddy

# Check status
sudo systemctl status caddy

โœ… Verify HTTPS โ€‹

Wait 30-60 seconds for certificates to be issued, then visit:

  • ๐Ÿ”’ https://admin.yourdomain.com โ€” should show the Admin login
  • ๐Ÿ”’ https://order.yourdomain.com โ€” should show the Storefront
  • ๐Ÿ”’ https://api.yourdomain.com/api/health โ€” should return {"status":"ok"}

๐Ÿ“‹ Caddy Logs โ€‹

bash
sudo journalctl -u caddy -f

๐Ÿ…ฑ๏ธ Option B: Nginx + Certbot โ€‹

If you prefer Nginx (the industry standard) or already have it installed, follow this section.

๐Ÿ“ฆ Install Nginx โ€‹

bash
sudo apt install -y nginx
sudo systemctl enable nginx

๐Ÿ”’ Install Certbot โ€‹

Certbot automates Let's Encrypt certificate issuance and renewal.

bash
sudo apt install -y certbot python3-certbot-nginx

โš™๏ธ Create Nginx Configuration โ€‹

First, expose the Docker container ports on 127.0.0.1 only. Edit docker-compose.prod.yml:

yaml
  server:
    ports:
      - "127.0.0.1:3000:3000"
  admin:
    ports:
      - "127.0.0.1:5173:80"
  storefront:
    ports:
      - "127.0.0.1:5174:80"

Restart Docker Compose:

bash
docker compose -f docker-compose.prod.yml up -d

Now create the Nginx site configs:

bash
sudo nano /etc/nginx/sites-available/kitchenasty

Paste the following:

nginx
# โ”€โ”€ Admin Dashboard โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
server {
    listen 80;
    server_name admin.yourdomain.com;

    location / {
        proxy_pass http://127.0.0.1:5173;
        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 API requests to the backend
    location /api/ {
        proxy_pass http://127.0.0.1:3000;
        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 uploaded images
    location /uploads/ {
        proxy_pass http://127.0.0.1:3000;
    }

    # WebSocket support for Socket.IO (kitchen display, live orders)
    location /socket.io/ {
        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_read_timeout 86400;
    }
}

# โ”€โ”€ Customer Storefront โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
server {
    listen 80;
    server_name order.yourdomain.com;

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

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

    location /uploads/ {
        proxy_pass http://127.0.0.1:3000;
    }

    location /socket.io/ {
        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_read_timeout 86400;
    }
}

# โ”€โ”€ API Server (optional dedicated subdomain) โ”€โ”€โ”€โ”€โ”€โ”€โ”€
server {
    listen 80;
    server_name api.yourdomain.com;

    client_max_body_size 10M;

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

    location /socket.io/ {
        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_read_timeout 86400;
    }
}

โœ… Enable the Site โ€‹

bash
# Enable the config
sudo ln -s /etc/nginx/sites-available/kitchenasty /etc/nginx/sites-enabled/

# Remove the default site
sudo rm -f /etc/nginx/sites-enabled/default

# Test the configuration
sudo nginx -t
# Should say: syntax is ok / test is successful

# Reload Nginx
sudo systemctl reload nginx

๐Ÿ” Obtain SSL Certificates โ€‹

Run Certbot to automatically get certificates and modify the Nginx config for HTTPS:

bash
sudo certbot --nginx \
  -d admin.yourdomain.com \
  -d order.yourdomain.com \
  -d api.yourdomain.com \
  --non-interactive \
  --agree-tos \
  --email your-email@example.com

Certbot will:

  1. โœ… Verify you own each domain (via HTTP challenge)
  2. ๐Ÿ”’ Obtain certificates from Let's Encrypt
  3. โš™๏ธ Modify the Nginx config to use HTTPS
  4. ๐Ÿ”„ Add automatic HTTP-to-HTTPS redirects

โ™ป๏ธ Verify Automatic Renewal โ€‹

Certbot sets up a systemd timer for automatic renewal. Verify it's active:

bash
sudo systemctl status certbot.timer

# Test renewal (dry run)
sudo certbot renew --dry-run

Let's Encrypt certificates are valid for 90 days. Certbot renews them automatically when they have less than 30 days remaining.

โœ… Verify HTTPS โ€‹

Visit your sites in a browser:

  • ๐Ÿ”’ https://admin.yourdomain.com
  • ๐Ÿ”’ https://order.yourdomain.com
  • ๐Ÿ”’ https://api.yourdomain.com/api/health

You should see a padlock icon in the address bar.


๐Ÿ”— Updating CORS After SSL Setup โ€‹

Now that your sites use HTTPS, update the CORS_ORIGINS in your .env file:

dotenv
CORS_ORIGINS=https://admin.yourdomain.com,https://order.yourdomain.com

Then restart the API server:

bash
docker compose -f docker-compose.prod.yml restart server

๐Ÿ”ง Troubleshooting โ€‹

โŒ "Connection refused" errors โ€‹

Ensure the Docker containers are running and ports are exposed on 127.0.0.1:

bash
docker compose -f docker-compose.prod.yml ps
curl http://127.0.0.1:3000/api/health

๐Ÿ” Certificate issuance fails โ€‹

  • โœ… Ensure DNS records point to your server (check with dig admin.yourdomain.com)
  • โœ… Ensure ports 80 and 443 are open in the firewall (sudo ufw status)
  • โœ… Ensure no other process is using port 80 (sudo lsof -i :80)

๐Ÿ”Œ WebSocket connections fail โ€‹

If the kitchen display or live order tracking doesn't work, verify the /socket.io/ proxy block is present in your config and includes the Upgrade and Connection headers.

โžก๏ธ Next Step โ€‹

Continue to Backups to set up automatic database backups.