Deploy Multiple wordPress Sites on One Server with Docker & Nginx. Master the art of hosting unlimited wordPress sites on a single server using containerization and reverse proxy routing.
Highlight: This guide demonstrates how to host multiple wordPress sites on a single server using Docker containers and Nginx reverse proxy. You will master port mapping strategies, domain-based routing, isolated container networks, and resource management for running unlimited sites efficiently and securely on one machine.
Running multiple wordPress sites traditionally meant either purchasing separate servers for each site or complex virtual host configurations that risked cross-contamination between sites. The containerized approach revolutionizes multi-site hosting by providing complete isolation while maximizing resource efficiency. Each website runs in its own self-contained environment, yet all sites share the same server infrastructure and public-facing ports.
This architecture is perfect for web agencies managing client sites, developers running staging and production environments, businesses with multiple web properties, or anyone who wants to consolidate their infrastructure. By the end of this guide, You will have a scalable framework that can grow from two sites to dozens without architectural changes.
The key insight is the reverse proxy pattern: a single Nginx instance examines incoming HTTP requests, reads the requested domain name from the Host header, and intelligently routes traffic to the appropriate backend container. Each site listens on a unique internal port (8060, 8090, 8100, etc.), but all sites are accessible through standard ports 80 and 443 from the internet.
Table of Contents
ToggleArchitecture Overview: Multi-Site Deployment
The multi-site architecture builds upon the single-site deployment by replicating the containerized wordPress stack for each additional site. Each stack operates independently with its own database, wordPress installation, and web server, yet all stacks coexist harmoniously on the same physical or virtual machine.
The diagram below illustrates how multiple completely isolated wordPress installations share a single server and a single global Nginx reverse proxy. Notice how each site has its own dedicated Docker Compose stack with separate containers, volumes, and networks - providing fortress-like isolation while maintaining efficient resource utilization.
Multi-Site Architecture Diagram
Key Architectural Components:
- Multiple Browsers: Different users accessing different domains (service.analyticalman.com and anothersite.com) all reach the same server IP address through DNS.
- Single Entry Point: One global Nginx instance handles all incoming traffic on ports 80 (HTTP) and 443 (HTTPS), acting as the traffic controller.
- Domain-Based Routing: Global Nginx examines the Host header in each request and routes to port 8060 for service.analyticalman.com or port 8090 for anothersite.com.
- Isolated Docker Stacks: Each site runs in completely separate Docker Compose environments with dedicated MySQL, wordPress, and Nginx containers.
- Separate Networks: Each site has its own Docker network (app-network-site1, app-network-site2), ensuring network-level isolation between sites.
- Independent Volumes: Database and wordPress files are stored in site-specific Docker volumes, preventing any data crossover.
- Unique Port Mapping: The container of each site Nginx maps to a unique host port (8060, 8090, 8100, etc.), enabling unlimited site hosting.
Architecture Benefits
This multi-site pattern provides exceptional benefits that traditional hosting approaches cannot match:
- Complete Isolation: A compromised site cannot affect other sites. Security vulnerabilities, plugin conflicts, and resource exhaustion are contained within individual stacks.
- Independent Lifecycles: Update, restart, or troubleshoot one site without touching others. Each site can run different wordPress versions, PHP versions, or configurations.
- Resource Efficiency: All containers share the host kernel and resources. The intelligent resource allocation of Docker ensures fair usage while allowing burst capacity when needed.
- Simplified Management: Standardized deployment procedures apply to all sites. Add a new site by copying configuration templates and changing a few variables.
- Cost Optimization: Run dozens of sites on a single server that would traditionally require multiple VPS instances. Significant savings on hosting costs and management overhead.
- Professional Infrastructure: This is the same architecture used by hosting companies and large organizations managing numerous web properties.
Prerequisites
Before proceeding with multi-site deployment, you need a properly configured server foundation. If you have not already set up the base infrastructure, complete these prerequisites first:
Required: Single Site Deployment
This guide builds upon the single-site wordPress deployment. You must have already completed:
- Ubuntu server with updated packages
- Global Nginx installed and running
- Docker and Docker Compose installed
- At least one wordPress site deployed and operational
- SSL certificates configured with Certbot
- Basic understanding of Docker concepts and Nginx configuration
If you have not completed these steps, refer to our comprehensive guide: Complete Guide: Deploying wordPress with Docker, Nginx & SSL and return here once you have a working single-site installation.
Domain Names
Each website requires its own domain name or subdomain. Ensure you have:
- Domain names registered and accessible
- DNS A records pointing to the IP address of your server for each domain
- DNS propagation completed (can take 24-48 hours)
- Ability to add more DNS records as you add sites
Server Resources
Consider resource requirements for multiple sites:
- Minimum per site: 512MB RAM, 10GB disk space
- Recommended: 2GB+ RAM for 3-5 sites, 4GB+ for 5-10 sites
- CPU: 2+ cores recommended for multiple sites
- Disk: SSD storage for better database performance
- Bandwidth: Adequate network capacity for combined site traffic
Step 1: Plan Your Port Mapping Strategy
The foundation of multi-site deployment is a consistent port mapping strategy. Each wordPress site needs a unique port number that does not conflict with other services. Planning this in advance prevents confusion and makes troubleshooting easier.
Port Assignment Convention
Establish a clear naming and numbering scheme for your sites:
| Domain Name | Internal Port | Container Prefix | Directory Name |
|---|---|---|---|
| service.analyticalman.com | 8060 | site1_ | site1-service |
| anothersite.com | 8090 | site2_ | site2-another |
| example.com | 8100 | site3_ | site3-example |
| demo.com | 8110 | site4_ | site4-demo |
Port Selection Guidelines:
- Use ports 8000-8999 for web applications (unprivileged range)
- Increment by 10 (8060, 8070, 8080...) or 30 (8060, 8090, 8120...) for easy tracking
- Document port assignments in a spreadsheet or configuration management system
- Avoid ports already used by system services (check with
sudo netstat -tulpn) - Reserve port ranges for future expansion
Directory Organization
Create a logical directory structure for managing multiple sites:
cd /home/your-user
mkdir wordpress-sites
cd wordpress-sites
# Create directories for each site
mkdir site1-service.analyticalman.com
mkdir site2-anothersite.com
mkdir site3-example.com
This structure keeps all wordPress installations organized in one location, making backups, updates, and management significantly easier. Each directory will contain the docker-compose.yml, .env file, and Nginx configuration of that site.
Step 2: Deploy Second wordPress Site
Now, with your port mapping planned and directories created, deploy the second wordPress site. The process closely mirrors the first deployment but with critical changes to prevent conflicts.
Create Second Site Directory and Files
cd /home/your-user/wordpress-sites/site2-anothersite.com
Create the docker-compose.yml file for the second site. Notice the critical differences from the first site:
version: '3'
services:
db:
image: mysql:8.3
container_name: site2_db
restart: unless-stopped
env_file: .env
environment:
- MYSQL_DATABASE=wordpress
volumes:
- site2_dbdata:/var/lib/mysql
command: '--default-authentication-plugin=mysql_native_password'
networks:
- site2-network
wordpress:
depends_on:
- db
image: wordpress:php8.3-fpm-alpine
container_name: site2_wordpress
restart: unless-stopped
env_file: .env
environment:
- WORDPRESS_DB_HOST=db:3306
- WORDPRESS_DB_USER=$MYSQL_USER
- WORDPRESS_DB_PASSWORD=$MYSQL_PASSWORD
- WORDPRESS_DB_NAME=wordpress
volumes:
- site2_wordpress:/var/www/html
networks:
- site2-network
webserver:
depends_on:
- wordpress
image: nginx:1.25.4-alpine
container_name: site2_webserver
restart: unless-stopped
ports:
- "8090:80"
volumes:
- site2_wordpress:/var/www/html
- ./nginx-conf:/etc/nginx/conf.d
networks:
- site2-network
volumes:
site2_wordpress:
site2_dbdata:
networks:
site2-network:
driver: bridge
Critical Changes Explained:
- Container Names: Changed from
sam_dbtosite2_db, etc. Each container needs a globally unique name. - Port Mapping: Changed from
8060:80to8090:80. This is the most critical change - no two containers can bind to the same host port. - Volume Names: Changed from
wordpresstosite2_wordpressanddbdatatosite2_dbdata. Separate volumes ensure complete data isolation. - Network Name: Changed from
app-networktosite2-network. Each site gets its own isolated network, preventing cross-site container communication.
Critical: Failing to change container names, ports, volumes, or networks will cause conflicts with your existing site. Docker will refuse to start containers with duplicate names or port bindings. Always ensure complete uniqueness across all these identifiers.
Create Environment File
Each site needs its own .env file with unique database credentials:
nano .env
MYSQL_ROOT_PASSWORD=different_secure_root_password
MYSQL_USER=site2_user
MYSQL_PASSWORD=different_secure_password
Security Best Practice: Use completely different passwords for the database of each site. If one site is compromised, unique credentials prevent lateral movement to other sites. Consider using a password manager to generate and store strong, unique passwords for each installation.
Create Container Nginx Configuration
mkdir nginx-conf
cd nginx-conf
nano nginx.conf
server {
listen 80;
listen [::]:80;
server_name anothersite.com;
index index.php index.html index.htm;
root /var/www/html;
location ~ /.well-known/acme-challenge {
allow all;
root /var/www/html;
}
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass wordpress:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
location ~ /\.ht {
deny all;
}
location = /favicon.ico {
log_not_found off;
access_log off;
}
location = /robots.txt {
log_not_found off;
access_log off;
allow all;
}
location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ {
expires max;
log_not_found off;
}
}
The only change from the first site is the server_name directive, which now specifies anothersite.com instead of service.analyticalman.com. This configuration remains identical otherwise because it operates within the isolated environment of the container.
Launch Second Site Containers
cd /home/your-user/wordpress-sites/site2-anothersite.com
sudo docker compose up -d
sudo docker ps
Verify all containers are running. You should now see six containers total: three for the first site (site1_db, site1_wordpress, site1_webserver) and three for the second site (site2_db, site2_wordpress, site2_webserver).
Verification: Test the second site by accessing it directly through its port: http://your-server-ip:8090. You should see the wordPress installation screen. If you get connection errors, check Docker logs: sudo docker logs site2_webserver
Step 3: Configure Global Nginx for Multiple Sites
The global Nginx must now route traffic for multiple domains. Each domain gets its own configuration file that specifies which backend port to forward traffic to.
Create Second Site Nginx Configuration
cd /etc/nginx/sites-available
sudo nano anothersite.com
server {
server_name anothersite.com www.anothersite.com;
listen [::]:80;
listen 80;
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://127.0.0.1:8090$request_uri;
}
}
Key Configuration Points:
- server_name: Matches
anothersite.comandwww.anothersite.com. Nginx uses this to determine which backend to route to. - proxy_pass: Points to
http://127.0.0.1:8090- the second site port. This is the critical routing decision. - Proxy Headers: Identical across all sites, preserving client information through the proxy.
Domain Matching: Nginx matches the server_name directive against the Host header in incoming HTTP requests. Now, when a browser requests anothersite.com, Nginx finds this server block and routes to port 8090. Now when a browser requests service.analyticalman.com, Nginx finds the other server block and routes to port 8060.
Enable Second Site Configuration
cd /etc/nginx/sites-enabled
sudo ln -s /etc/nginx/sites-available/anothersite.com .
ls -la
You should now see symbolic links for both sites in the sites-enabled directory. This confirms both configurations are active.
Test and Reload Nginx
sudo nginx -t
sudo systemctl reload nginx
The nginx -t command validates both site configurations. If successful, reload Nginx to activate the new routing rules. Use reload instead of restart to avoid dropping existing connections.
Testing: Both sites should now be accessible through their domain names (assuming DNS is configured). Visit http://anothersite.com and http://service.analyticalman.com - each should display its respective wordPress installation page.
Step 4: Configure SSL for All Sites
Each domain requires its own SSL certificate. Fortunately, Certbot makes obtaining certificates for multiple domains straightforward.
Obtain SSL Certificate for Second Site
sudo certbot --nginx
Now when prompted, Certbot will display a list of all configured domains found in your Nginx configuration:
which names would you like to activate HTTPS for?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: service.analyticalman.com
2: anothersite.com
3: www.anothersite.com
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate numbers separated by commas and/or spaces, or leave input
blank to select all options shown (Enter 'c' to cancel):
Select the second site domains (options 2 and 3 in this example) by entering: 2,3
Certbot Activities:
- Verifies domain ownership through ACME challenge
- Generates SSL certificate for anothersite.com and www.anothersite.com
- Installs certificate in
/etc/letsencrypt/live/anothersite.com/ - Automatically modifies
/etc/nginx/sites-available/anothersite.comto enable HTTPS - Adds automatic redirect from HTTP to HTTPS
- Configures secure SSL settings (protocols, ciphers)
Certificate Management: Certbot creates separate certificate files for each domain in /etc/letsencrypt/live/. Each certificate auto-renews independently. Monitor renewal status with: sudo certbot certificates
Verify HTTPS Configuration
Test both sites with HTTPS:
- Visit
https://service.analyticalman.com- should display green padlock - Visit
https://anothersite.com- should display green padlock - Try HTTP versions - should automatically redirect to HTTPS
- Check certificate details in browser - each site should have its own valid certificate
# Test certificate expiration dates
sudo certbot certificates
# Verify auto-renewal works
sudo certbot renew --dry-run
Step 5: Add Additional Sites
Now, with two sites operational, adding a third, fourth, or tenth site follows the exact same pattern. The process becomes mechanical once you understand the template.
Repeatable Deployment Template
For each new site, follow this checklist:
1. Plan Site Parameters
- Domain name:
example.com - Port number:
8100(next available port) - Container prefix:
site3_ - Directory:
site3-example.com
2. Create Directory Structure
cd /home/your-user/wordpress-sites
mkdir site3-example.com
cd site3-example.com
mkdir nginx-conf
3. Copy and Modify docker-compose.yml
cp ../site2-anothersite.com/docker-compose.yml .
nano docker-compose.yml
Update these values:
- All container names:
site3_db,site3_wordpress,site3_webserver - Port mapping:
8100:80 - Volume names:
site3_wordpress,site3_dbdata - Network name:
site3-network
4. Create .env File
nano .env
Add unique database credentials.
5. Create Container Nginx Config
cp ../site2-anothersite.com/nginx-conf/nginx.conf nginx-conf/
nano nginx-conf/nginx.conf
Change server_name to example.com.
6. Launch Containers
sudo docker compose up -d
sudo docker ps | grep site3
7. Configure Global Nginx
sudo cp /etc/nginx/sites-available/anothersite.com /etc/nginx/sites-available/example.com
sudo nano /etc/nginx/sites-available/example.com
Change server_name to example.com and proxy_pass to http://127.0.0.1:8100.
8. Enable Site and Reload
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
9. Configure SSL
sudo certbot --nginx
Select the new domain from the list.
Congratulations! You have added another site. This process scales indefinitely - add as many sites as your server resources allow. Each site remains completely isolated and independently manageable.
Resource Management for Multiple Sites
As you add more sites, resource management becomes critical. Docker provides tools to prevent any single site from monopolizing system resources.
Container Resource Limits
Add resource constraints to your docker-compose.yml files to ensure fair resource distribution:
services:
db:
image: mysql:8.3
container_name: site2_db
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
Resource Limit Explanation:
- limits: Maximum resources a container can use. Prevents resource hogging.
- cpus: '0.5': Container can use up to 50% of one CPU core.
- memory: 512M: Container limited to 512MB RAM.
- reservations: Minimum guaranteed resources Docker will try to provide.
Apply similar limits to wordPress and Nginx containers based on the traffic and resource requirements of your site.
Monitoring Resource Usage
Monitor container resource consumption to identify bottlenecks:
# View resource usage for all containers
sudo docker stats
# View usage for specific site
sudo docker stats site2_db site2_wordpress site2_webserver
# Check container logs for errors
sudo docker logs --tail 100 site2_wordpress
# Inspect container details
sudo docker inspect site2_wordpress
The docker stats command shows real-time CPU, memory, network I/O, and disk I/O for each container. Use this data to adjust resource limits and identify sites that need optimization or more powerful hosting.
Backup Strategy for Multiple Sites
Backing up multiple sites requires systematic organization to prevent confusion and ensure comprehensive coverage.
Automated Backup Script
Create a bash script to backup all sites automatically:
#!/bin/bash
# Multi-site wordPress backup script
BACKUP_DIR="/backup/wordpress-sites"
DATE=$(date +%Y%m%d-%H%M%S)
# Create backup directory
mkdir -p $BACKUP_DIR/$DATE
# Backup Site 1
docker exec site1_db mysqldump -u root -p$MYSQL_ROOT_PASSWORD wordpress > $BACKUP_DIR/$DATE/site1-db.sql
docker run --rm --volumes-from site1_wordpress -v $BACKUP_DIR/$DATE:/backup ubuntu tar czf /backup/site1-files.tar.gz /var/www/html
# Backup Site 2
docker exec site2_db mysqldump -u root -p$MYSQL_ROOT_PASSWORD wordpress > $BACKUP_DIR/$DATE/site2-db.sql
docker run --rm --volumes-from site2_wordpress -v $BACKUP_DIR/$DATE:/backup ubuntu tar czf /backup/site2-files.tar.gz /var/www/html
# Backup configurations
cp -r /home/your-user/wordpress-sites $BACKUP_DIR/$DATE/configs
cp -r /etc/nginx/sites-available $BACKUP_DIR/$DATE/nginx-configs
# Compress entire backup
tar czf $BACKUP_DIR/backup-$DATE.tar.gz -C $BACKUP_DIR $DATE
rm -rf $BACKUP_DIR/$DATE
# Delete backups older than 30 days
find $BACKUP_DIR -name "backup-*.tar.gz" -mtime +30 -delete
echo "Backup completed: $BACKUP_DIR/backup-$DATE.tar.gz"
Save this script as /home/your-user/backup-all-sites.sh and make it executable:
chmod +x /home/your-user/backup-all-sites.sh
Automate with Cron
crontab -e
# Add daily backup at 2 AM
0 2 * * * /home/your-user/backup-all-sites.sh >> /var/log/wordpress-backup.log 2>&1
Troubleshooting Multi-Site Deployments
Common Issues and Solutions
Problem: Port Conflict Error
Error response from daemon: driver failed programming external connectivity on endpoint site2_webserver: Bind for 0.0.0.0:8090 failed: port is already allocated
Solution: Another service is using port 8090. Check with sudo netstat -tulpn | grep 8090 and either stop the conflicting service or choose a different port in docker-compose.yml.
Problem: wrong site displays
Symptom: Visiting anothersite.com shows content from service.analyticalman.com.
Solution: Global Nginx configuration issue. Verify server_name and proxy_pass directives are correct. Test with: curl -H "Host: anothersite.com" http://localhost:8090
Problem: SSL certificate for wrong domain
Symptom: Browser shows certificate error - certificate is for different domain.
Solution: Certbot installed certificate for wrong server block. Run sudo certbot delete to remove incorrect certificate, then run sudo certbot --nginx and carefully select correct domains.
Problem: Container Name Conflict
Error response from daemon: Conflict. The container name "/site1_db" is already in use
Solution: Container names must be unique across all docker-compose files. Change all container names in the docker-compose.yml of the new site to use a different prefix.
Problem: High Memory Usage
Symptom: Server becomes slow, containers crash due to OOM (Out Of Memory) errors.
Solution: Implement resource limits in docker-compose.yml. Upgrade server RAM or reduce number of sites. Monitor with docker stats and optimize wordPress (disable unused plugins, implement caching).
Scaling Beyond Single Server
If a single server reaches capacity, consider these scaling strategies:
Vertical Scaling
- Upgrade server CPU, RAM, and disk
- Move to more powerful VPS or dedicated server
- Optimize containers: enable OPcache, implement Redis caching, optimize MySQL
- Add more storage for growing databases and media files
Horizontal Scaling
- Database Offloading: Move MySQL databases to dedicated database server (Aws RDS, managed MySQL)
- Load Balancing: Deploy multiple application servers behind a load balancer
- Shared Storage: Use NFS or cloud storage (Aws EFS) for wordPress files shared across servers
- CDN Integration: Offload static assets to CloudFlare, CloudFront, or similar CDN
- Docker Swarm/Kubernetes: Orchestrate containers across multiple servers for high availability
Separation Strategy
- Dedicate high-traffic sites to their own servers
- Group low-traffic sites on shared infrastructure
- Separate production and staging environments across different servers
- Use geographic distribution: EU sites on EU servers, US sites on US servers
Best Practices Summary
Documentation
- Maintain a spreadsheet tracking all sites: domain, port, container names, database credentials location
- Document any custom configurations or modifications
- Keep DNS records documented with TTL settings
- Record SSL certificate expiration dates (even though auto-renewal is configured)
Security
- Use unique, strong passwords for the database of each site
- Store .env files securely, never commit to version control
- Implement fail2ban to block brute force attacks
- Keep all containers updated: regularly pull new images and rebuild
- Monitor security advisories for wordPress, themes, and plugins
- Consider implementing intrusion detection (AIDE, Tripwire)
Performance
- Implement wordPress caching plugins on all sites
- Add Redis or Memcached for object caching
- Enable PHP OPcache in wordPress containers
- Optimize images before upload or use image optimization plugins
- Monitor slow database queries and optimize
- Use CDN for static assets
- Implement lazy loading for images and videos
Maintenance
- Schedule regular backups (daily for databases, weekly for files)
- Test backup restoration quarterly
- Monitor disk space usage:
df -h - Review Docker logs weekly for errors
- Update wordPress core, themes, and plugins monthly
- Clean up old Docker images and volumes:
docker system prune - Review and rotate logs to prevent disk space exhaustion
Conclusion
You have now mastered deploying multiple wordPress sites on a single server using Docker and Nginx reverse proxy. This architecture provides enterprise-grade isolation, security, and scalability while maintaining simplicity and cost-effectiveness.
The pattern you have learned scales from 2 sites to 100+ sites with the same fundamental approach. Each site operates independently with its own database, files, and containerized environment, yet all sites efficiently share the underlying server infrastructure. The global Nginx reverse proxy elegantly routes traffic based on domain names, presenting a seamless experience to users while maintaining complete backend separation.
This infrastructure approach is used by hosting companies, web development agencies, and enterprises worldwide. You are now equipped to manage sophisticated multi-site wordPress hosting with professional-grade tools and practices.
Next Step: Consider implementing advanced features: automated deployment pipelines with Git hooks, monitoring dashboards with Grafana, centralized logging with ELK stack, or container orchestration with Docker Swarm. The foundation you have built supports unlimited growth and sophistication.
💬 Feedback & Support
Loved the discussion? Have suggestions? Found a bug?
- Blog: analyticalman.com
- Issues: Open a GitHub issue
- Contact: analyticalman.com
Acknowledgments
- Legacy design: @Pangolier
- The open-source community for amazing codes



