Deploy WordPress with Docker, Nginx, MySQL, and Let's Encrypt SSL
Production-ready WordPress on Docker: a docker-compose with MySQL 8 and PHP-FPM, Nginx as the reverse proxy, Let's Encrypt SSL via Certbot, plus Redis caching and a backup strategy.
“It works on my server” is not a deployment story. The classic WordPress setup — PHP from the OS, a system MySQL, an Nginx someone configured two years ago — is hard to reproduce and harder to move. Docker fixes that by making the whole stack a single docker-compose.yml you can rebuild anywhere in a few seconds.
This is the WordPress + MySQL + Nginx + Let’s Encrypt setup I keep coming back to.
Why Docker for WordPress
A containerized stack solves four specific problems with the traditional setup:
- Local, staging, and prod all run the exact same PHP and MySQL versions, so “works on my machine” bugs go away.
- Each service (WordPress, MySQL, Nginx, Redis) restarts and scales independently.
- The whole site moves to a new host with
docker compose up, plus a volume restore. - The config lives in one file you can put in Git instead of in shell history.
Prerequisites
Before getting started, make sure you have:
- Docker installed
- Docker Compose installed
- A domain name for SSL setup
- Basic command-line knowledge
- Access to your server or local development environment
Step 1: Create the Docker Compose Setup
Create a docker-compose.yml file in your project directory.
version: "3.8"
services:
db:
image: mysql:8.0
volumes:
- db_data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: root_password
MYSQL_DATABASE: wordpress
MYSQL_USER: wp_user
MYSQL_PASSWORD: wp_password
networks:
- wordpress_net
wordpress:
depends_on:
- db
image: wordpress:php8.1-fpm-alpine
volumes:
- wordpress_data:/var/www/html
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wp_user
WORDPRESS_DB_PASSWORD: wp_password
WORDPRESS_DB_NAME: wordpress
networks:
- wordpress_net
nginx:
image: nginx:latest
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- wordpress_data:/var/www/html
- ./certbot/etc/letsencrypt:/etc/letsencrypt
depends_on:
- wordpress
networks:
- wordpress_net
volumes:
db_data:
wordpress_data:
networks:
wordpress_net:
The file defines three services: MySQL stores the database in a named volume, the WordPress container runs PHP-FPM, and Nginx terminates HTTP/HTTPS and proxies PHP requests to the WordPress container over port 9000.
Step 2: Configure Nginx and SSL
Next, create an Nginx configuration file.
Create the directory:
mkdir -p nginx/conf.d
Then create the file:
nvim nginx/conf.d/wordpress.conf
Add the following configuration:
server {
listen 80;
server_name yourdomain.com;
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
root /var/www/html;
index index.php;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
fastcgi_pass wordpress:9000;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
This configuration redirects HTTP traffic to HTTPS and passes PHP requests to the WordPress container.
Replace yourdomain.com with your actual domain name.
Step 3: Generate SSL Certificates with Certbot
Use Certbot to generate SSL certificates for your domain.
docker run -it --rm --name certbot \
-v "./certbot/etc/letsencrypt:/etc/letsencrypt" \
-v "./certbot/var/lib/letsencrypt:/var/lib/letsencrypt" \
-v "./nginx/html:/var/www/html" \
certbot/certbot certonly --webroot -w /var/www/html -d yourdomain.com
Replace yourdomain.com with your real domain.
To renew certificates later, you can run Certbot again. In some cases, you may use --force-renewal when you need to force certificate renewal.
Step 4: Launch the WordPress Stack
Start all services in detached mode:
docker-compose up -d
Check that the containers are running:
docker-compose ps
Once everything is running, visit your site:
https://yourdomain.com
You should see the WordPress installation screen.
Optimization Tips
After WordPress is running, you can improve performance, reliability, and security with a few additional steps.
1. Add Redis Caching
Redis can improve WordPress performance by caching database queries and objects.
Add Redis to your docker-compose.yml file:
redis:
image: redis:alpine
networks:
- wordpress_net
Then configure WordPress with a plugin such as Redis Object Cache.
Redis is especially useful for high-traffic WordPress sites or WooCommerce stores.
2. Optimize Image Uploads
Large images can slow down your website. Use image optimization plugins such as Smush or ShortPixel to compress media files.
You can also increase PHP upload limits by creating an uploads.ini file:
upload_max_filesize = 256M
post_max_size = 256M
memory_limit = 512M
Mount this file into the WordPress PHP configuration directory if your setup requires larger uploads.
3. Harden WordPress Security
Security should be part of every WordPress deployment.
Recommended steps include:
- Use strong database passwords.
- Keep WordPress, themes, and plugins updated.
- Limit login attempts.
- Use Cloudflare firewall rules for DDoS protection.
- Disable unused plugins and themes.
- Restrict file permissions where possible.
You can also use tools like Fail2Ban to help block repeated malicious login attempts.
4. Set Up a Backup Strategy
Backups are essential for production WordPress sites.
Back Up MySQL
docker exec db sh -c 'exec mysqldump --all-databases -uroot -p"$MYSQL_ROOT_PASSWORD"' > backup.sql
Back Up WordPress Files
docker cp wordpress:/var/www/html ./wordpress_backup
For production, schedule regular backups with cron and store copies off-server.
A good backup strategy should include:
- Daily database backups
- Regular WordPress file backups
- Off-site storage
- Backup restoration testing
5. Monitor Container Performance
Monitoring helps you identify performance bottlenecks before they become major problems.
You can use tools like Prometheus and Grafana to track:
- CPU usage
- Memory usage
- Container uptime
- Network traffic
- Disk usage
For simpler environments, Docker’s built-in stats command can also help:
docker stats
Troubleshooting Common Issues
If something goes wrong, start by checking the most common problem areas.
Database Connection Failures
Make sure WORDPRESS_DB_HOST matches the MySQL service name in your Docker Compose file.
WORDPRESS_DB_HOST: db:3306
Also verify that the database username, password, and database name match between the db and wordpress services.
File Permission Errors
If WordPress cannot write files, fix ownership inside the WordPress container:
chown -R www-data:www-data /var/www/html
You may need to run this command inside the WordPress container depending on your setup.
SSL Errors
If SSL does not work, check:
- Certbot volume mounts
- Nginx certificate paths
- Domain DNS records
- Firewall rules for ports
80and443
Also confirm that the certificate files exist in the expected path:
ls ./certbot/etc/letsencrypt/live/yourdomain.com
Nginx or WordPress Container Not Starting
Check container logs:
docker-compose logs nginx
docker-compose logs wordpress
docker-compose logs db
Logs usually reveal configuration errors, missing files, or database connection problems.
Wrap-up
The compose file is the source of truth: WordPress + PHP-FPM, MySQL with a persistent volume, Nginx with Let’s Encrypt SSL, optionally Redis for object cache. Take a backup before every deploy, and don’t expose the MySQL port to the public network.
Once the basic stack is stable, the next things I’d reach for are Traefik (so SSL renewal is automatic), a CDN in front of Nginx, and an off-host backup cron.
Related
- How to factory-reset Docker — when something gets stuck and you need a clean Docker before redeploying.
- Arch Linux: setting ACLs for proper file permissions — fixes the permission problems that often follow WordPress + Nginx volumes.