1

Currently, I want to run multiple wordpress sites with multiple domains in 1 vps. I hope I can separate each wordpress in separated sub folders.

I tried with many different ways but I always got 404 error at the first time I visit to setup wordpress.

Here is my error log in nginx error.log

*6 FastCGI sent in stderr: "Primary script unknown" while reading response header from upstream, client: 192.168.63.1, server: site1.com, request: "GET / HTTP/1.0", upstream: "fastcgi://172.21.0.4:9000", host: "192.168.63.130"

Could anyone explain to me what I'm doing wrong? Thank you!

docker-compose.yml

version: '3.6'
services:
  nginx:
    image: nginx:1.15.7-alpine
    container_name: nginx
    ports:
      - '80:80'
      - '443:443'
    volumes:
      - ./nginx:/etc/nginx
      - ./logs/nginx:/var/log/nginx
      - ./wordpress:/var/www/html
      - ./certs:/etc/letsencrypt
      - ./certs-data:/data/letsencrypt
    links:
      - site1
      - site2
    restart: always

  mysql:
    image: mariadb
    container_name: mysql
    volumes:
      - ./mysql:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=password
    restart: always

  site1:
    image: wordpress:php7.2-fpm-alpine
    container_name: site1
    volumes:
      - ./wordpress/site1:/var/www/html/
      - ./php/conf.d/uploads.ini:/usr/local/etc/php/conf.d/uploads.ini
    environment:
      - WORDPRESS_DB_NAME=site1
      - WORDPRESS_TABLE_PREFIX=wp_
      - WORDPRESS_DB_HOST=mysql
      - WORDPRESS_DB_PASSWORD=password
    links:
      - mysql
    restart: always

  site2:
    image: wordpress:php7.2-fpm-alpine
    container_name: site2
    volumes:
      - ./wordpress/site2:/var/www/html/
      - ./php/conf.d/uploads.ini:/usr/local/etc/php/conf.d/uploads.ini
    environment:
      - WORDPRESS_DB_NAME=site2
      - WORDPRESS_TABLE_PREFIX=wp_
      - WORDPRESS_DB_HOST=mysql
      - WORDPRESS_DB_PASSWORD=password
    links:
      - mysql
    restart: always

/nginx/sites-enabled/site1 (site2 is the same with replace site1 to site2

fastcgi_cache_path /var/cache/nginx/site1 levels=1:2 keys_zone=site1:100m inactive=60m;

server {
        # Ports to listen on
        listen 80;
        listen [::]:80;

        # Server name to listen for
        server_name site1.com;

        # Path to document root
        root /var/www/html/site1;

        # File to be used as index
        index index.php;

        # Overrides logs defined in nginx.conf, allows per site logs.
        access_log /var/log/nginx/site1.access.log;
        error_log /var/log/nginx/site1.error.log crit;

        # Default server block rules
        include global/server/defaults.conf;

        # Fastcgi cache rules
        include global/server/fastcgi-cache.conf;

        location / {
                try_files $uri $uri/ /index.php?$args;
        }

        location ~ \.php$ {
                try_files $uri =404;
                include global/fastcgi-params.conf;

                fastcgi_pass   site1:9000;

                # Skip cache based on rules in global/server/fastcgi-cache.conf.
                fastcgi_cache_bypass $skip_cache;
                fastcgi_no_cache $skip_cache;

                # Define memory zone for caching. Should match key_zone in fastcgi_cache_path above.
                fastcgi_cache site1;

                # Define caching time.
                fastcgi_cache_valid 60m;
        }

        # Rewrite robots.txt
        rewrite ^/robots.txt$ /index.php last;

        # Uncomment if using the fastcgi_cache_purge module and Nginx Helper plugin (https://wordpress.org/plugins/nginx-helper/)
        # location ~ /purge(/.*) {
        #       fastcgi_cache_purge fastcgi-cache.com "$scheme$request_method$host$1";
        # }
}

# Redirect www to non-www
server {
        listen 80;
        listen [::]:80;
        server_name www.site1.com;

        return 301 $scheme://site1.com$request_uri;
}
1
  • 1
    Did you ever solve this? Commented Apr 3, 2019 at 9:09

3 Answers 3

8

Maybe it will help someone. I successfully deployed multiple (domains) WordPress docker containers with single Nginx docker.

docker-compose.yml:

version: '3'

services:
  db:
    image: mysql:8.0
    container_name: db
    restart: unless-stopped
    env_file: .env
    environment:
      - MYSQL_DATABASE=wordpress
    volumes:
      - dbdata:/var/lib/mysql
      - ./mysql:/docker-entrypoint-initdb.d
    command: '--default-authentication-plugin=mysql_native_password'
    networks:
      - app-network

  wordpress1:
    depends_on:
      - db
    image: wordpress:6.0.1-fpm-alpine
    container_name: wordpress1
    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=wordpress1
    volumes:
      - wordpress1:/var/www/html
    networks:
      - app-network

  wordpress2:
    depends_on:
      - db
    image: wordpress:6.0.1-fpm-alpine
    container_name: wordpress2
    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=wordpress2
    volumes:
      - wordpress2:/var/www/html
    networks:
      - app-network

  webserver:
    depends_on:
      - wordpress1
      - wordpress2
    image: nginx:1.15.12-alpine
    container_name: webserver
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - wordpress1:/var/www/domain1.com
      - wordpress2:/var/www/domain2.com
      - ./nginx-conf:/etc/nginx/conf.d
      - certbot-etc:/etc/letsencrypt
    networks:
      - app-network

  certbot:
    depends_on:
      - webserver
    image: certbot/dns-cloudflare
    container_name: certbot
    volumes:
      - certbot-etc:/etc/letsencrypt
      - ./certbot:/etc/letsencrypt-conf
    command: certonly -v --cert-name domain1.com --dns-cloudflare --dns-cloudflare-credentials /etc/letsencrypt-conf/cloudflareapi.cfg --agree-tos --email [email protected] --no-eff-email --non-interactive --server https://acme-v02.api.letsencrypt.org/directory -d "*.domain1.com" -d domain1.com

#  certbot2:
#  ...

volumes:
  certbot-etc:
  wordpress1:
  wordpress2:
  dbdata:

networks:
  app-network:
    driver: bridge

nginx-conf/domain1.com.conf (wordpress2 site nginx config is pretty much the same - just replace domain1 with domain2)

server {
    listen 80;
    listen [::]:80;

    server_name domain1.com www.domain1.com;

    location ~ /.well-known/acme-challenge {
        allow all;
        root /var/www/domain1.com;
    }

    if ($host = www.domain1.com) {
        return 301 https://$host$request_uri;
    }
    if ($host = domain1.com) {
        return 301 https://$host$request_uri;
    }

    return 404;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl  http2 ipv6only=on;

    server_name domain1.com www.domain1.com;

    root /var/www/domain1.com;
    index index.php index.html index.htm;

    server_tokens off;

    ssl_certificate /etc/letsencrypt/live/domain1.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/domain1.com/privkey.pem;
    include /etc/nginx/conf.d/options-ssl-nginx.conf;

    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "no-referrer-when-downgrade" always;
    add_header Content-Security-Policy "default-src * data: 'unsafe-eval' 'unsafe-inline'" always;
    # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
    # enable strict transport security only if you understand the implications

    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 /var/www/html$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;
    }
}

I spent hours if not days to figure out I need to change:

fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

to

fastcgi_param SCRIPT_FILENAME /var/www/html$fastcgi_script_name;

Hope I will spare some extra hours to anyone.

Sign up to request clarification or add additional context in comments.

3 Comments

This looks really nice dude!
won't the wordpress service in the nginx config will be of the wordpress service of the respective site?
chat gpt couldn't help me, but you definitely did!
2

I add a Dockerfile site1_build/Dockerfile:

FROM  wordpress:php7.2-fpm
WORKDIR /var/www/html/site1

In the docker-compose.yml file:

 site1:
    build: ./site1_build
    container_name: site1
    volumes:
      - ./wordpress/site1:/var/www/html/site1

2 Comments

Am I correct in assuming that you would of course have built two Dockerfiles? Site1 and Site2.
You need different container per site. You can create many containers using the same Dockerfile but change the name of the container during the build
0

Uros Majeric's answer helped me (not enough reputation to comment)

However, I had to change each of the Docker volume in the Wordpress container to exactly match the nginx, ie.

  wordpress1:
  ###
    volumes:
      - wordpress1:/var/www/domain1.com

  wordpress2:
  ###
    volumes:
      - wordpress2:/var/www/domain2.com

1 Comment

The point is not to mount the volume to the same directory as wordpress Docker has its files in /var/www/html folder. I had the same config before as you wrote here, but because of this I had other issues. This is why SCRIPT_FILENAME in nginx config is pointing to the /var/www/html$fastcgi_script_name;!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.