Nginx cannot access files from wordpress container and presents a 403

I am trying to setup a local development environment to locally develop a plugin and theme for a Wordpress site. I’ve used different setups with docker-compose but encountered serious performance issues locally. Recently I am using Local wordpress dev example and that works like a charm. Except for one thing. It seems that the local mounted file (src and import) is not accessible by the Nginx container. I am running the latest version of Docker desktop (4.34.2) for Windows (11).

I run the docker image using the following docker-compose.yml file:

services:
  nginx:
    container_name: ${CONTAINER_NAME}-nginx
    build:
      context: .
      dockerfile: ./nginx/Dockerfile
    restart: unless-stopped
    env_file: .env
    environment:
      HOSTNAME: ${HOSTNAME}
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - wordpress:/var/www/html
    networks:
      - internal

  database:
    container_name: ${CONTAINER_NAME}-database
    image: mysql:8.0
    restart: unless-stopped
    env_file: .env
    environment:
      MYSQL_DATABASE: ${DATABASE_NAME}
      MYSQL_PASSWORD: ${DATABASE_PASSWORD}
      MYSQL_ROOT_PASSWORD: ${DATABASE_ROOT_PASSWORD}
      MYSQL_USER: ${DATABASE_USER}
    healthcheck:
      test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost", "-u", "root", "-p$$DATABASE_ROOT_PASSWORD" ]
      timeout: 20s
      retries: 10
    ports:
      - "3306:3306"
    volumes:
      - dbdata:/var/lib/mysql
    networks:
      - internal

  phpmyadmin:
    container_name: ${CONTAINER_NAME}-phpmyadmin
    image: phpmyadmin/phpmyadmin
    env_file: .env
    environment:
      PMA_HOST: database
      PMA_PORT: 3306
      MYSQL_ROOT_PASSWORD: "${DATABASE_ROOT_PASSWORD}"
    ports:
      - "8081:80"
    networks:
      - internal

  wordpress:
    depends_on:
      - database
    container_name: ${CONTAINER_NAME}-wordpress
    image: wordpress:6.6.2-fpm-alpine
    restart: unless-stopped
    env_file: .env
    environment:
      WORDPRESS_DB_HOST: database:3306 # use the same name as database service
      WORDPRESS_DB_NAME: '${DATABASE_NAME}'
      WORDPRESS_DB_USER: '${DATABASE_USER}'
      WORDPRESS_DB_PASSWORD: '${DATABASE_PASSWORD}'
      WORDPRESS_DEBUG: 1
    volumes:
      - wordpress:/var/www/html
      - ./src:/var/www/html/wp-content:rw
      - ./import:/var/www/html/import:rw
    networks:
      - internal
volumes:
  dbdata:
  wordpress:

networks:
  internal:
    driver: bridge

With the following Nginx config:

upstream php {
  server unix:/tmp/php-cgi.socket;
  server wordpress:9000;
}

server {
  listen   80; ## listen for ipv4; this line is default and implied
  listen   [::]:80; ## listen for ipv6
  server_name ${HOSTNAME};

  root /var/www/html;

  location / {
    index index.php index.html;
  }

  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;
  }
}

When accessing the /wp-content file in the browser is shows a 403 permission denied:

403 Forbidden / nginx/1.27.1

Also the directory is empty when check the files in the Nginx container (same is the case for the import folder). The Wordpress container is able to list all the files. This makes that the Wordpress installation is running, but it is not able to for instance show images.

I’ve tried to rebuild the containers. Also clean with no cache. I’ve also made sure that the owner and permissions of the folder are the same (upon build they are assigned to root instead of www-data).
I’ve ran following commands in Wordpress container:

$ chown -R www-data:www-data /var/www/html/wp-content/

$ chmod -R 755 /var/www/html/wp-content/

However, files are still not mirrored in the Nginx container and both folders (wp-content + import) remain empty in the Nginx container, and therefore not accessible via the browser.

Any suggestion how to fix this issue?

try modify nginx location:

    location / {
        # First attempt to serve request as file, then
        # as directory, then fall back to index.php
        try_files $uri $uri/ /index.php$is_args$args;
    }

Unfortunately this yields directly an error directly on every folder (403 permission denied):
2024/09/24 14:05:55 [error] 9#9: *1 directory index of "/var/www/html/" is forbidden, client: 172.18.0.1
This is also the case for opening any existing subfolder (e.g. /wp-content) within the browsers.

…and did you tried open sh from nginx container and look into the …/html directory, if you are able to see the right directory content?

Yes, content is present. When I list the html directory it shows:

root@4084c7b2e793:/var/www/html# ls -la
total 268
drwxr-xr-x  6   82   82  4096 Sep 20 11:45 .
drwxr-xr-x  3 root root  4096 Sep 24 14:05 ..
-rw-r--r--  1   82   82   261 Sep 12 22:03 .htaccess
-rw-r--r--  1   82   82   405 Feb  6  2020 index.php
-rw-r--r--  1   82   82 19915 Jan  1  2024 license.txt
-rw-r--r--  1   82   82  7409 Jun 18 11:59 readme.html
drwxr-xr-x  2 root root  4096 Sep 20 11:45 import
-rw-r--r--  1   82   82  7387 Feb 13  2024 wp-activate.php
drwxr-xr-x  9   82   82  4096 Sep 10 15:23 wp-admin
-rw-r--r--  1   82   82   351 Feb  6  2020 wp-blog-header.php
-rw-r--r--  1   82   82  2323 Jun 14  2023 wp-comments-post.php
-rw-r--r--  1   82   82  5512 Sep 12 22:01 wp-config-docker.php
-rw-r--r--  1   82   82  3033 Mar 11  2024 wp-config-sample.php
-rw-r--r--  1   82   82  5616 Sep 20 11:45 wp-config.php
drwxrwxrwt  5   82   82  4096 Sep 20 11:45 wp-content
-rw-r--r--  1   82   82  5638 May 30  2023 wp-cron.php
drwxr-xr-x 30   82   82 16384 Sep 10 15:23 wp-includes
-rw-r--r--  1   82   82  2502 Nov 26  2022 wp-links-opml.php
-rw-r--r--  1   82   82  3937 Mar 11  2024 wp-load.php
-rw-r--r--  1   82   82 51238 May 28 11:13 wp-login.php
-rw-r--r--  1   82   82  8525 Sep 16  2023 wp-mail.php
-rw-r--r--  1   82   82 28774 Jul  9 15:43 wp-settings.php
-rw-r--r--  1   82   82 34385 Jun 19  2023 wp-signup.php
-rw-r--r--  1   82   82  4885 Jun 22  2023 wp-trackback.php
-rw-r--r--  1   82   82  3246 Mar  2  2024 xmlrpc.php

Both /import and /wp-content folder also remain empty:

root@4084c7b2e793:/var/www/html/import# ls -la
total 8
drwxr-xr-x 2 root root 4096 Sep 20 11:45 .
drwxr-xr-x 6   82   82 4096 Sep 20 11:45 ..

…and when you will remove this 2 volumes:

      - ./src:/var/www/html/wp-content:rw
      - ./import:/var/www/html/import:rw

Is wordpress working and are those files inside import or wp-content available?

Not surprising when you don’t mount those folders into the nginx container, only the wordpress container. Mount is not copying files. What happened is probably that you used a common volume mounted to both containers and tried to bind mount two more folders onto the exitsing volume, which means that empty folders have to be created as mount points which is indeed a change on the volume so you see it in the other container, but the mount happens only in the wordpress container. Each container has its own mount namespace. That is why containers can have their own root filesystem. You have to mount the folders to the nginx container as well or configure the nginx proxy to send all requests to the wordpress container where you write a php script or module that handles these requests even when there is no PHP there to handle and the php script can then handle static contents and use something like readfile to send the content to the client immediately.

But the easiest way is mounting the volumes in both containers so nginx knows about the files.

Why not simply use a standard WordPress container image with a webserver included?

Thanks for your reply. And indeed this was the case. Now everything works like a charm! Such an easy solution, but I was walking for miles in the wrong direction. Thank you!

Docker compose file now looks like this:

services:
  database:
    container_name: ${CONTAINER_NAME}-database
    image: mysql:8.0
    restart: unless-stopped
    env_file: .env
    environment:
      MYSQL_DATABASE: ${DATABASE_NAME}
      MYSQL_PASSWORD: ${DATABASE_PASSWORD}
      MYSQL_ROOT_PASSWORD: ${DATABASE_ROOT_PASSWORD}
      MYSQL_USER: ${DATABASE_USER}
    healthcheck:
      test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost", "-u", "root", "-p$$DATABASE_ROOT_PASSWORD" ]
      timeout: 20s
      retries: 10
    ports:
      - "3306:3306"
    volumes:
      - dbdata:/var/lib/mysql
    networks:
      - internal

  phpmyadmin:
    container_name: ${CONTAINER_NAME}-phpmyadmin
    image: phpmyadmin/phpmyadmin
    env_file: .env
    environment:
      PMA_HOST: database
      PMA_PORT: 3306
      MYSQL_ROOT_PASSWORD: "${DATABASE_ROOT_PASSWORD}"
    ports:
      - "8081:80"
    networks:
      - internal

  wordpress:
    depends_on:
      - database
    container_name: ${CONTAINER_NAME}-wordpress
    image: wordpress:6.6.2-fpm-alpine
    restart: unless-stopped
    env_file: .env
    environment:
      WORDPRESS_DB_HOST: database:3306 # use the same name as database service
      WORDPRESS_DB_NAME: '${DATABASE_NAME}'
      WORDPRESS_DB_USER: '${DATABASE_USER}'
      WORDPRESS_DB_PASSWORD: '${DATABASE_PASSWORD}'
      WORDPRESS_DEBUG: 1
    volumes:
      - wordpress:/var/www/html
      - ./src:/var/www/html/wp-content:rw
      - ./import:/var/www/html/import
    networks:
      - internal

  nginx:
    container_name: ${CONTAINER_NAME}-nginx
    build:
      context: .
      dockerfile: ./nginx/Dockerfile
    restart: unless-stopped
    env_file: .env
    environment:
      HOSTNAME: ${HOSTNAME}
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - wordpress:/var/www/html
      - ./src:/var/www/html/wp-content:rw      
      - ./import:/var/www/html/import      
    networks:
      - internal

volumes:
  dbdata:
  wordpress:

networks:
  internal:
    driver: bridge

1 Like

Due to performance issues. This was my previous setup, but every page load took at least 3 seconds if not longer. That’s why I tried a different approach. And this works way way faster for local development.