Caddy on host + forwarding port for php_fpm into a container, rewrite to index.php not working

1. The problem I’m having:

I am trying to run the image wordpress:php8.0-fpm
and have caddy running on the host.

Can this be done? Do I need to share path the php files are served from between php_fpm and caddy?

As I have seen that others run caddy in a container as well and share the volume for the php files between the containers.

With my current setup of forwarding port 9001 from the host into the container for php_fpm I have the issue that http requests to / are not rewritten to index.php properly.

curl -vL http://phptest.localhost/

gives an error. See log.

curl -vL http://phptest.localhost/index.php

no error and the file from the container is returned.

root@vm1 /s/phptest# curl -vL http://phptest.localhost/
*   Trying 127.0.0.1:80...
* Connected to phptest.localhost (127.0.0.1) port 80 (#0)
> GET / HTTP/1.1
> Host: phptest.localhost
> User-Agent: curl/7.88.1
> Accept: */*
> 
< HTTP/1.1 308 Permanent Redirect
< Connection: close
< Location: https://phptest.localhost/
< Server: Caddy
< Date: Mon, 04 Nov 2024 22:24:04 GMT
< Content-Length: 0
< 
* Closing connection 0
* Clear auth, redirects to port from 80 to 443
* Issue another request to this URL: 'https://phptest.localhost/'
*   Trying 127.0.0.1:443...
* Connected to phptest.localhost (127.0.0.1) port 443 (#1)
* ALPN: offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256
* ALPN: server accepted h2
* Server certificate:
*  subject: [NONE]
*  start date: Nov  4 20:33:12 2024 GMT
*  expire date: Nov  5 08:33:12 2024 GMT
*  subjectAltName: host "phptest.localhost" matched cert's "phptest.localhost"
*  issuer: CN=Caddy Local Authority - ECC Intermediate
*  SSL certificate verify ok.
* using HTTP/2
* h2h3 [:method: GET]
* h2h3 [:path: /]
* h2h3 [:scheme: https]
* h2h3 [:authority: phptest.localhost]
* h2h3 [user-agent: curl/7.88.1]
* h2h3 [accept: */*]
* Using Stream ID: 1 (easy handle 0xaaaaee4baaf0)
> GET / HTTP/2
> Host: phptest.localhost
> user-agent: curl/7.88.1
> accept: */*
> 
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
< HTTP/2 404 
< alt-svc: h3=":443"; ma=2592000
< server: Caddy
< content-length: 0
< date: Mon, 04 Nov 2024 22:24:04 GMT
< 
* Connection #1 to host phptest.localhost left intact
root@vm1 /s/phptest# 

When I create a index.php contaning echo "Hello from index.php on the host system 👾 :)"; on the host in the same path, caddy will now correctly access the index.php from the container with the content echo "I am traped in this container heeeelp..."

But this cant be a solution?

I have tried to add rewrite or handle configurations but so far with no success.

Calls to specific paths always work as intended curl -vL http://phptest.localhost/index.php or curl -vL http://phptest.localhost/db_test.php

I have also installed caddy inside the wordpress fpm container and these issues are not present when using the same caddy configuration and making the http request from inside the container.

root@vm1 /s/phptest# mv index.php_old index.php
root@vm1 /s/phptest# cat index.php
<?php
echo "Hello from index.php on the host system 👾 :)";
?>
root@vm1 /s/phptest# curl -vL http://phptest.localhost/
*   Trying 127.0.0.1:80...
* Connected to phptest.localhost (127.0.0.1) port 80 (#0)
> GET / HTTP/1.1
> Host: phptest.localhost
> User-Agent: curl/7.88.1
> Accept: */*
> 
< HTTP/1.1 308 Permanent Redirect
< Connection: close
< Location: https://phptest.localhost/
< Server: Caddy
< Date: Mon, 04 Nov 2024 22:28:12 GMT
< Content-Length: 0
< 
* Closing connection 0
* Clear auth, redirects to port from 80 to 443
* Issue another request to this URL: 'https://phptest.localhost/'
*   Trying 127.0.0.1:443...
* Connected to phptest.localhost (127.0.0.1) port 443 (#1)
* ALPN: offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256
* ALPN: server accepted h2
* Server certificate:
*  subject: [NONE]
*  start date: Nov  4 20:33:12 2024 GMT
*  expire date: Nov  5 08:33:12 2024 GMT
*  subjectAltName: host "phptest.localhost" matched cert's "phptest.localhost"
*  issuer: CN=Caddy Local Authority - ECC Intermediate
*  SSL certificate verify ok.
* using HTTP/2
* h2h3 [:method: GET]
* h2h3 [:path: /]
* h2h3 [:scheme: https]
* h2h3 [:authority: phptest.localhost]
* h2h3 [user-agent: curl/7.88.1]
* h2h3 [accept: */*]
* Using Stream ID: 1 (easy handle 0xaaaadfc2faf0)
> GET / HTTP/2
> Host: phptest.localhost
> user-agent: curl/7.88.1
> accept: */*
> 
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
< HTTP/2 200 
< alt-svc: h3=":443"; ma=2592000
< content-type: text/html; charset=UTF-8
< server: Caddy
< x-powered-by: PHP/8.0.30
< content-length: 60
< date: Mon, 04 Nov 2024 22:28:12 GMT
< 
PHP executing...
I am traped in this container heeeelp...
* Connection #1 to host phptest.localhost left intact
root@vm1 /s/phptest# 

2. Error messages and/or full log output:

2024/11/04 22:57:39.578 INFO    http.log.access handled request {"request": {"remote_ip": "127.0.0.1", "remote_port": "50246", "proto": "HTTP/1.1", "method": "GET", "host": "phptest.localhost", "uri": "/", "headers": {"User-Agent": ["curl/7.88.1"], "Accept": ["*/*"]}}, "user_id": "", "duration": 0.00004508, "size": 0, "status": 308, "resp_headers": {"Server": ["Caddy"], "Connection": ["close"], "Location": ["https://phptest.localhost/"], "Content-Type": []}}
2024/11/04 22:57:39.632 INFO    http.log.access handled request {"request": {"remote_ip": "127.0.0.1", "remote_port": "59568", "proto": "HTTP/2.0", "method": "GET", "host": "phptest.localhost", "uri": "/", "headers": {"Accept": ["*/*"], "User-Agent": ["curl/7.88.1"]}, "tls": {"resumed": false, "version": 772, "cipher_suite": 4865, "proto": "h2", "server_name": "phptest.localhost"}}, "user_id": "", "duration": 0.00217768, "size": 60, "status": 200, "resp_headers": {"X-Powered-By": ["PHP/8.0.30"], "Content-Type": ["text/html; charset=UTF-8"], "Server": ["Caddy"], "Alt-Svc": ["h3=\":443\"; ma=2592000"]}}
2024/11/04 22:58:19.361 INFO    http.log.access handled request {"request": {"remote_ip": "127.0.0.1", "remote_port": "56064", "proto": "HTTP/1.1", "method": "GET", "host": "phptest.localhost", "uri": "/", "headers": {"User-Agent": ["curl/7.88.1"], "Accept": ["*/*"]}}, "user_id": "", "duration": 0.00003784, "size": 0, "status": 308, "resp_headers": {"Server": ["Caddy"], "Connection": ["close"], "Location": ["https://phptest.localhost/"], "Content-Type": []}}
2024/11/04 22:58:19.433 ERROR   http.log.access handled request {"request": {"remote_ip": "127.0.0.1", "remote_port": "49962", "proto": "HTTP/2.0", "method": "GET", "host": "phptest.localhost", "uri": "/", "headers": {"User-Agent": ["curl/7.88.1"], "Accept": ["*/*"]}, "tls": {"resumed": false, "version": 772, "cipher_suite": 4865, "proto": "h2", "server_name": "phptest.localhost"}}, "user_id": "", "duration": 0.00015248, "size": 0, "status": 404, "resp_headers": {"Server": ["Caddy"], "Alt-Svc": ["h3=\":443\"; ma=2592000"]}}

3. Caddy version:

2.6.2-5

4. How I installed and ran Caddy:

apt on debian 12:
apt policy caddy

 caddy:
   Installed: 2.6.2-5
   Candidate: 2.6.2-5
   Version table:
  *** 2.6.2-5 500
         500 http://debian.anexia.at/debian bookworm/main arm64 Packages
         100 /var/lib/dpkg/status

a. System environment:

debian 12
nerdctl & containerd with wordpress:php8.0-fpm

root@vm1 /s/phptest# nerdctl ps -a
0a7144ad7c4d    docker.io/library/wordpress:php8.0-fpm    "docker-entrypoint.s…"    44 hours ago    Up                         0.0.0.0:9001->9000/tcp    wp-jessica-wordpress-1

b. Command:

On the host:
root@vm1 /s/phptest# caddy run --config /etc/caddy/Caddyfile

c. Service/unit/compose file:

compose.yaml

services:
  wordpress:
    image: wordpress:php8.0-fpm
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_NAME: wordpress_db
      WORDPRESS_DB_USER: wordpress_user
      WORDPRESS_DB_PASSWORD: wordpress_password
    volumes:
      - wp_data:/var/www/html
    ports:
      - "9001:9000"  # Expose PHP-FPM port 9000 to the host at port 9001

  db:
    image: mariadb:latest
    environment:
      MYSQL_DATABASE: wordpress_db
      MYSQL_USER: wordpress_user
      MYSQL_PASSWORD: wordpress_password
      MYSQL_ROOT_PASSWORD: root_password
    volumes:
      - wp_db_data:/var/lib/mysql

volumes:
  wordpress_data:
  db_data:

d. My complete Caddy config:

phptest.localhost {
        root * /srv/phptest/
    
        # port 9001 is forwarded into a container:
        # nerdctl ps -a
        # 0a7144ad7c4d    docker.io/library/wordpress:php8.0-fpm  
        # / "docker-entrypoint.s…"    42 hours ago    Up 
        # / 0.0.0.0:9001->9000/tcp    wordpress-1
        php_fastcgi localhost:9001

        file_server
        log
}

Yep, Caddy needs to be able to see the files in order to determine whether an index rewrite is appropriate. More than that, the path should be the same between the two - e.g. in /var/www/html in both container AND Caddy host - or you’ll need to configure it further so Caddy can tell FPM about the file path discrepancy.

If it can’t see any PHP files, it won’t send them to FastCGI upstream (which only handles *.php files) - the request will fall through to the file server, and that will return 404 because presumably there’s nothing on disk.

Without giving Caddy the information it needs, it can’t make the right decisions about how to handle the requests. You really need to give Caddy access to those files anyway; WordPress FPM doesn’t actually serve the static files, it just executes the PHP and leaves the file serving to Caddy, and - well - it needs those files to serve.

3 Likes

Ah thanks that makes a lot of sense now! :slight_smile:

So what is good solution here given I want to run two Wordpress containers, so the files can’t be mounted on the host in the same location.

Configure Caddy to tell FPM about the file path discrepancy? How would I do that? Is that a bad idea?

Or should I create a Dockerfile that moves the files in each of the Wordpress the image so they are at a different location?

Or just do what everyone else does I guess, run a separate web server in its own container for each Wordpress container and just set up a reverse proxy in the Caddy running on the host?

1 Like

Not a bad idea at all, it’s a perfectly normal use case. In fact, there is an example for exactly that in our docs at https://caddyserver.com/docs/caddyfile/directives/php_fastcgi#docker.

It shows you how to use the root subdirective for php_fastcgi.

3 Likes

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.