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.

Soman Bandesha Updated 6 min read
Deploy WordPress with Docker, Nginx, MySQL, and Let's Encrypt SSL

“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 80 and 443

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.