How to Host Multiple Ghost Blogs on a Single Server with Docker and Nginx
Learn how to efficiently host multiple Ghost blogs on a single server using Docker, Nginx, and MySQL. This guide covers setting up a scalable, secure environment with HTTPS via Let’s Encrypt.
This guide demonstrates how to host multiple Ghost blogs on a single Ubuntu server using Docker, Nginx as a reverse proxy, and MySQL as the database backend. We’ll also secure the setup with Let’s Encrypt for HTTPS.
In this example, we’ll host two Ghost blogs on the following subdomains:
- Blog 1:
blog.mydomain1.com
- Blog 2:
blog.mydomain2.com
By following this setup, you can easily scale and add more blogs in the future.
Prerequisites:
- A basic understanding of Linux and Docker.
- An Ubuntu server. (We used a $12/month DigitalOcean droplet with 2 GB RAM and 50 GB SSD.)
- Access to DNS settings to configure subdomains.
Step 1: Prepare Your Server
- Update Your Server. Ensure your server is updated and ready:
sudo apt update && sudo apt upgrade -y
- Configure DNS. Add the following DNS A records to your domain provider:
blog.mydomain1.com → [Your Server IP]
blog.mydomain2.com → [Your Server IP]
If you’re using Cloudflare: Go to the SSL/TLS section and set the mode to Full or Full (Strict). Disable "Automatic HTTPS Rewrites" and "Always Use HTTPS" if they are enabled.
- Set Up HTTPS with Let’s Encrypt. Install Certbot and generate SSL certificates:
sudo apt install certbot python3-certbot-nginx -y
sudo certbot certonly --standalone -d blog.mydomain1.com -d blog.mydomain2.com
Using --standalone
mode, Certbot starts a temporary web server to complete the validation. Ensure that ports 80 and 443 are not in use (e.g., temporarily stop Nginx or Apache if they are running).
You can then verify that the certificates were created by listing the contents of the directory:
ls /etc/letsencrypt/live/
Step 2: Configure Docker Compose
Install Docker and Docker Compose to run Docker containers:
sudo apt install docker.io -y
sudo apt install docker-compose -y
Create a working directory to hold your configurations:
mkdir docker-dir
cd docker-dir
Create a docker-compose.yml
file to define your services. Open the file for editing:nano docker-compose.yml
Insert the following configuration:
version: "3.1"
services:
reverse-proxy:
image: nginx
restart: always
container_name: reverse-proxy
volumes:
- /root/docker-dir/nginx.conf:/etc/nginx/nginx.conf
- /etc/letsencrypt:/etc/letsencrypt
- /etc/ssl:/etc/ssl
ports:
- 80:80
- 443:443
ghost-blog1:
image: ghost:latest
restart: always
container_name: ghost-blog1
ports:
- 2368:2368
volumes:
- ./ghost-blog1/content:/var/lib/ghost/content
environment:
NODE_ENV: production
database__client: mysql
database__connection__host: mysql-ghost-db
database__connection__user: root
database__connection__password: <password>
database__connection__database: ghost_blog1
url: https://blog.mydomain1.com
ghost-blog2:
image: ghost:latest
restart: always
container_name: ghost-blog2
ports:
- 2369:2368
volumes:
- ./ghost-blog2/content:/var/lib/ghost/content
environment:
NODE_ENV: production
database__client: mysql
database__connection__host: mysql-ghost-db
database__connection__user: root
database__connection__password: <password>
database__connection__database: ghost_blog2
url: https://blog.mydomain2.com
mysql-ghost-db:
image: mysql:8.0
restart: always
container_name: mysql-ghost-db
environment:
MYSQL_ROOT_PASSWORD: <password>
volumes:
- ghost-database:/var/lib/mysql
volumes:
ghost-database:
Save and exit the file.
Step 3: Configure Nginx as a Reverse Proxy
Create an Nginx configuration file to route traffic to the correct Ghost container. Open the file:nano nginx.conf
Insert the following configuration:
events {
worker_connections 1024;
}
http {
# Redirect HTTP to HTTPS for blog.mydomain1.com
server {
listen 80;
server_name blog.mydomain1.com;
location / {
return 301 https://$host$request_uri;
}
}
# HTTPS server block for blog.mydomain1.com
server {
listen 443 ssl;
server_name blog.mydomain1.com;
ssl_certificate /etc/letsencrypt/live/blog.mydomain1.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/blog.mydomain1.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
location / {
proxy_pass http://ghost-blog1:2368;
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 https;
}
}
# Redirect HTTP to HTTPS for blog.mydomain2.com
server {
listen 80;
server_name blog.mydomain2.com;
location / {
return 301 https://$host$request_uri;
}
}
# HTTPS server block for blog.mydomain2.com
server {
listen 443 ssl;
server_name blog.mydomain2.com;
ssl_certificate /etc/letsencrypt/live/blog.mydomain2.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/blog.mydomain2.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
location / {
proxy_pass http://ghost-blog2:2368;
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 https;
}
}
}
Save and exit the file.
Step 4: Start the Services
Start your services using Docker Compose: docker-compose up -d
Don't forget to set up automatic backups! Many server providers offer built-in backup features that can help you recover your data in case of unexpected issues.
Bonus: Automatically Renew Let's Encrypt SSL Certificates
Setting up automatic renewal for your Let's Encrypt SSL certificates ensures that your blog stays secure without manual intervention. Here's how to configure it:
Step 1: Update Let's Encrypt Configuration
- Check your Let's Encrypt renewal configuration:
cat /etc/letsencrypt/renewal/blog.mydomain1.com.conf
- Modify the file to use the
webroot
authenticator. Add or update the following lines:
# ... other config ...
authenticator = webroot
webroot_path = /var/www/certbot
# ... other config ...
- Repeat this process for all your certificates.
Step 2: Update Nginx Configuration
Modify your Nginx configuration to support the ACME challenge for SSL certificate renewal:
server {
listen 80;
server_name blog.mydomain1.com blog.mydomain2.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
...
Step 3: Update Docker Compose
Ensure your Docker setup supports SSL renewal by sharing the webroot
directory with the reverse proxy container:
- Edit your
docker-compose.yml
file and add the following volume to the reverse proxy service:
reverse-proxy:
...
volumes:
...
- /var/www/certbot:/var/www/certbot # This is to renew Let's Encrypt certificates automatically
- Restart the Docker containers to apply changes:
docker-compose down
docker-compose up -d
Step 4: Simulate Renewal
Run a dry-run of the certificate renewal to verify everything is configured correctly:
sudo certbot renew --dry-run
If the dry-run completes successfully, your certificates will renew automatically in the future.
Bonus: Useful Docker and Ubuntu Commands
Here are some useful commands to help you manage your setup and troubleshoot common issues:
View Logs for Services
Check the logs for specific containers to troubleshoot errors or confirm that services are running smoothly:
docker logs ghost-blog1 # View logs for the first Ghost blog
docker logs ghost-blog2 # View logs for the second Ghost blog
docker logs reverse-proxy # View logs for the Nginx reverse proxy
docker logs mysql-ghost-db # View logs for the MySQL database
Stop All Services
Shut down all containers defined in your docker-compose.yml
:
docker-compose down
Start or Restart All Services
Start the containers in the background:
docker-compose up -d
Rebuild the containers (if needed) and start them in the background:
docker-compose up --build -d
Check Running Containers
List all active Docker containers to confirm they are running as expected:
docker ps
Restart a Specific Container
Restart a single container, such as the reverse proxy:
docker-compose restart reverse-proxy
Install Nano Text Editor
apt update && apt install nano -y
Wrapping Up
You now have a robust setup for hosting multiple Ghost blogs on a single server. This configuration uses Docker, MySQL for data storage, and Nginx as a reverse proxy, all secured with SSL from Let’s Encrypt. You can scale this by adding more Ghost services and updating the Nginx configuration.
Related articles: