b multiple site at ec2

Host Multiple Sites on One Aws Server

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.

Docker wordPress Nginx Multi-Site Reverse Proxy DevOps

Table of Contents

Architecture 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

multi site same aws ec2
Hosting multiple websites from the same Aws EC2 instance using docker and Nginx

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.

1

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_db to site2_db, etc. Each container needs a globally unique name.
  • Port Mapping: Changed from 8060:80 to 8090:80. This is the most critical change - no two containers can bind to the same host port.
  • Volume Names: Changed from wordpress to site2_wordpress and dbdata to site2_dbdata. Separate volumes ensure complete data isolation.
  • Network Name: Changed from app-network to site2-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.

2

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.

3

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.

4

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.

5

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.com and www.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.

6

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.

7

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.

8

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.com to 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

9

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?

Acknowledgments

  • Legacy design: @Pangolier
  • The open-source community for amazing codes

If this project helped you, consider giving it a ⭐ on GitHub!

Leave a Comment

Your email address will not be published. Required fields are marked *

Table of Contents

Index
Scroll to Top