[Guide] Running Discourse with WordPress (Docker) on a single VPS using Nginx Reverse Proxy

Introduction

By default, a Discourse “standalone” installation binds to ports 80 and 443. To host another application like WordPress on the same server, you must reconfigure Discourse to listen on an internal port and use a Host-level Nginx as a Reverse Proxy to manage traffic and SSL certificates.


1. Architecture Overview

  • Host Nginx: The primary gateway listening on ports 80 and 443. It handles SSL termination and routes requests to the appropriate container based on the server_name.

  • Discourse Container: Reconfigured to listen on localhost:8080.

  • WordPress Container: Managed via Docker Compose, listening on localhost:8081.


2. Phase A: Reconfiguring Discourse

Modify your /var/discourse/containers/app.yml to relinquish the public ports:

  1. Change Port Mapping:

    YAML

    expose:
      - "8080:80"   # Maps host port 8080 to container port 80
    
    
  2. Disable Internal SSL: Comment out the SSL and Let’s Encrypt templates:

    YAML

    templates:
      - "templates/postgres.template.yml"
      - "templates/redis.template.yml"
      - "templates/web.template.yml"
      # - "templates/web.ssl.template.yml"
      # - "templates/web.letsencrypt.ssl.template.yml"
    
    
  3. Rebuild: Run ./launcher rebuild app.


3. Phase B: Deploying WordPress via Docker Compose

Organize your WordPress site in a dedicated directory. Ensure the database volume is persistent to prevent data loss.

YAML

services:
  db:
    image: mariadb:10.11
    environment:
      MYSQL_ROOT_PASSWORD: 'your_secure_password'
    volumes:
      - ./mysql_data:/var/lib/mysql
  wordpress:
    image: wordpress:latest
    ports:
      - "8081:80"
    volumes:
      - .:/var/www/html


4. Phase C: Final Nginx Configuration (SSL & Port 443)

A professional setup in 2025 requires full HTTPS and HTTP/2 support. After installing Nginx on your host (sudo apt install nginx), create a configuration for your domains.

Pro Tip: Run sudo certbot --nginx to automatically generate the SSL blocks, but ensure they include the following proxy headers so Discourse functions correctly.

Nginx

server {
    listen 443 ssl http2;
    server_name discourse.com;

    # SSL Certs by Certbot
    ssl_certificate /etc/letsencrypt/live/discourse.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/discourse.com/privkey.pem;

    location / {
        proxy_pass http://localhost:8080;
        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; # Crucial for Discourse HTTPS detection
    }
}


5. Hard-Won Lessons & Best Practices

  • Database Credentials: If your passwords contain special characters like & or ?, always wrap them in single quotes ' ' in your config files and terminal commands to prevent shell interpretation errors.

  • File Permissions: WordPress containers run as www-data (UID 33). If you upload or unzip files as root, you must run chown -R 33:33 . to avoid 500 Internal Server Errors.

  • Cloudflare Settings: When using an on-server SSL certificate (Let’s Encrypt), set Cloudflare SSL/TLS to Full (Strict). This prevents the “Too Many Redirects” loop commonly caused by the “Flexible” mode.

  • Persistent Volumes: Never run docker compose down or rebuild without verifying your database files are stored in a persistent volume (e.g., ./mysql_data).


Conclusion: Decoupling your apps from ports 80/443 using a Reverse Proxy is the most scalable way to manage a multi-site VPS. It allows for centralized SSL management and easy debugging through host-level Nginx logs.