Serve one of two domains with a path suffix with php_fastcgi

1. The problem I’m having:

I’m trying to have my single codebase serve two domains, but when one domain is served, its simply treated the same as the other domain, but as if the requestor had asked for a certain prefixed path. E.g.

  • domain1.mydomain.com - served as nomral
  • domain2.mydomain.com - serves the same as if the user had gone to domain1.mydomain.com/my-subfolder

The other key factor is that I’m trying to do this without the user noticing. E.g. their browser still shows domain2.mydomain.com instead of sending them to domain1.mydomain.com/my-subfolder. Hence a simple redirect is not possible.

The reason for this is that the two sites share one codebase, and the entire second domain can simply be handled in Slim using the group as shown below:

$app = AppFactory::create();

$app->addRoutingMiddleware();

$app->addErrorMiddleware(true, true, true);

// Set the routes
$app->get('/', function (Request $request, Response $response, $args) {
    $body = $response->getBody();
    $body->write('On the main page...'); // returns number of bytes written
    $newResponse = $response->withBody($body);
    return $newResponse;
});

// second domain routes here
$app->group('/my-subfolder', function (RouteCollectorProxy $app) {
    $app->get('', function (Request $request, Response $response, $args) {
        $body = $response->getBody();
        $body->write('On the sub-site page...');
        $newResponse = $response->withBody($body);
        return $newResponse;
    });
});

// Run app
$app->run();

I have had various attempts with using uri to try and change the URI before passing it to php_fastcgi, but that appears to have no effect. I have also had no success so far with reverse proxying.

My latest Caddyfile configuration is currently:

# global options
{
    debug

    # Disable the automatic management of TLS certs with Let's Encrypt
    # https://caddyserver.com/docs/caddyfile/options#auto-https
    auto_https disable_certs
}


*.programster.org {
    tls /ssl/fullchain.pem /ssl/privkey.pem

    @foo host domain2.programster.org
    handle @foo {
        root * /var/www/site/public_html
        encode gzip
        #rewrite * /my-subfolder/{uri}
        uri path_regexp .* /my-subfolder{uri}
        php_fastcgi 127.0.0.1:9000
        file_server
    }

    @bar host domain1.programster.org
    handle @bar {
        root * /var/www/site/public_html
        encode gzip
        php_fastcgi 127.0.0.1:9000
        file_server
    }
}

2. Error messages and/or full log output:

At the moment, I’m not getting an error message. It just serves the content from the original domain (domain1.programster.org), instead of the rewritten path. The output from caddy (in my docker container) is:

demo  | {"level":"debug","ts":1712335895.1677234,"logger":"http.handlers.rewrite","msg":"rewrote request","request":{"remote_ip":"172.20.0.1","remote_port":"37088","client_ip":"172.20.0.1","proto":"HTTP/2.0","method":"GET","host":"domain2.programster.org","uri":"/","headers":{"Te":["trailers"],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"],"Accept-Language":["en-GB,en;q=0.5"],"Accept-Encoding":["gzip, deflate, br"],"Sec-Fetch-Mode":["navigate"],"Dnt":["1"],"Sec-Fetch-User":["?1"],"Pragma":["no-cache"],"Cookie":[],"Upgrade-Insecure-Requests":["1"],"Sec-Gpc":["1"],"Sec-Fetch-Dest":["document"],"Sec-Fetch-Site":["none"],"Cache-Control":["no-cache"]},"tls":{"resumed":true,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"domain2.programster.org"}},"method":"GET","uri":"/my-subfolder/"}
demo  | {"level":"debug","ts":1712335895.1677845,"logger":"http.handlers.rewrite","msg":"rewrote request","request":{"remote_ip":"172.20.0.1","remote_port":"37088","client_ip":"172.20.0.1","proto":"HTTP/2.0","method":"GET","host":"domain2.programster.org","uri":"/my-subfolder/","headers":{"Sec-Fetch-User":["?1"],"Pragma":["no-cache"],"Sec-Gpc":["1"],"Cookie":[],"Upgrade-Insecure-Requests":["1"],"Cache-Control":["no-cache"],"Sec-Fetch-Dest":["document"],"Sec-Fetch-Site":["none"],"Accept-Language":["en-GB,en;q=0.5"],"Accept-Encoding":["gzip, deflate, br"],"Sec-Fetch-Mode":["navigate"],"Dnt":["1"],"Te":["trailers"],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"]},"tls":{"resumed":true,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"domain2.programster.org"}},"method":"GET","uri":"/index.php"}
demo  | {"level":"debug","ts":1712335895.1677997,"logger":"http.handlers.reverse_proxy","msg":"selected upstream","dial":"127.0.0.1:9000","total_upstreams":1}
demo  | {"level":"debug","ts":1712335895.1678524,"logger":"http.reverse_proxy.transport.fastcgi","msg":"roundtrip","request":{"remote_ip":"172.20.0.1","remote_port":"37088","client_ip":"172.20.0.1","proto":"HTTP/2.0","method":"GET","host":"domain2.programster.org","uri":"/index.php","headers":{"Sec-Fetch-Dest":["document"],"Dnt":["1"],"Pragma":["no-cache"],"Te":["trailers"],"Sec-Fetch-User":["?1"],"X-Forwarded-For":["172.20.0.1"],"Cookie":[],"Accept-Encoding":["gzip, deflate, br"],"Sec-Fetch-Mode":["navigate"],"X-Forwarded-Proto":["https"],"X-Forwarded-Host":["domain2.programster.org"],"Accept-Language":["en-GB,en;q=0.5"],"Upgrade-Insecure-Requests":["1"],"Sec-Gpc":["1"],"Sec-Fetch-Site":["none"],"Cache-Control":["no-cache"],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"]},"tls":{"resumed":true,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"domain2.programster.org"}},"env":{"REQUEST_METHOD":"GET","SERVER_NAME":"domain2.programster.org","SERVER_SOFTWARE":"Caddy/v2.7.6","DOCUMENT_ROOT":"/var/www/site/public_html","HTTPS":"on","SSL_CIPHER":"TLS_AES_128_GCM_SHA256","HTTP_COOKIE":"","CONTENT_TYPE":"","CONTENT_LENGTH":"","HTTP_X_FORWARDED_FOR":"172.20.0.1","HTTP_X_FORWARDED_PROTO":"https","HTTP_ACCEPT_LANGUAGE":"en-GB,en;q=0.5","HTTP_SEC_GPC":"1","HTTP_SEC_FETCH_SITE":"none","HTTP_CACHE_CONTROL":"no-cache","GATEWAY_INTERFACE":"CGI/1.1","HTTP_USER_AGENT":"Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0","REQUEST_URI":"/","HTTP_DNT":"1","HTTP_PRAGMA":"no-cache","SERVER_PROTOCOL":"HTTP/2.0","REMOTE_HOST":"172.20.0.1","REMOTE_USER":"","SCRIPT_NAME":"/index.php","REMOTE_IDENT":"","SSL_PROTOCOL":"TLSv1.3","SCRIPT_FILENAME":"/var/www/site/public_html/index.php","DOCUMENT_URI":"/index.php","HTTP_HOST":"domain2.programster.org","HTTP_ACCEPT_ENCODING":"gzip, deflate, br","HTTP_SEC_FETCH_MODE":"navigate","HTTP_ACCEPT":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8","REMOTE_PORT":"37088","PATH_INFO":"","REQUEST_SCHEME":"https","HTTP_SEC_FETCH_DEST":"document","HTTP_TE":"trailers","HTTP_SEC_FETCH_USER":"?1","HTTP_X_FORWARDED_HOST":"domain2.programster.org","HTTP_UPGRADE_INSECURE_REQUESTS":"1","AUTH_TYPE":"","REMOTE_ADDR":"172.20.0.1","SERVER_PORT":"443","QUERY_STRING":""},"dial":"127.0.0.1:9000","env":{"HTTP_SEC_GPC":"1","HTTP_SEC_FETCH_SITE":"none","HTTP_CACHE_CONTROL":"no-cache","GATEWAY_INTERFACE":"CGI/1.1","CONTENT_LENGTH":"","HTTP_X_FORWARDED_FOR":"172.20.0.1","HTTP_X_FORWARDED_PROTO":"https","HTTP_ACCEPT_LANGUAGE":"en-GB,en;q=0.5","HTTP_USER_AGENT":"Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0","SERVER_PROTOCOL":"HTTP/2.0","REQUEST_URI":"/","HTTP_DNT":"1","HTTP_PRAGMA":"no-cache","REMOTE_IDENT":"","REMOTE_HOST":"172.20.0.1","REMOTE_USER":"","SCRIPT_NAME":"/index.php","SCRIPT_FILENAME":"/var/www/site/public_html/index.php","SSL_PROTOCOL":"TLSv1.3","HTTP_ACCEPT":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8","REMOTE_PORT":"37088","DOCUMENT_URI":"/index.php","HTTP_HOST":"domain2.programster.org","HTTP_ACCEPT_ENCODING":"gzip, deflate, br","HTTP_SEC_FETCH_MODE":"navigate","HTTP_SEC_FETCH_USER":"?1","HTTP_X_FORWARDED_HOST":"domain2.programster.org","HTTP_UPGRADE_INSECURE_REQUESTS":"1","AUTH_TYPE":"","PATH_INFO":"","REQUEST_SCHEME":"https","HTTP_SEC_FETCH_DEST":"document","HTTP_TE":"trailers","QUERY_STRING":"","REMOTE_ADDR":"172.20.0.1","SERVER_PORT":"443","HTTPS":"on","SSL_CIPHER":"TLS_AES_128_GCM_SHA256","HTTP_COOKIE":"","CONTENT_TYPE":"","REQUEST_METHOD":"GET","SERVER_NAME":"domain2.programster.org","SERVER_SOFTWARE":"Caddy/v2.7.6","DOCUMENT_ROOT":"/var/www/site/public_html"},"request":{"remote_ip":"172.20.0.1","remote_port":"37088","client_ip":"172.20.0.1","proto":"HTTP/2.0","method":"GET","host":"domain2.programster.org","uri":"/index.php","headers":{"Cookie":[],"Accept-Encoding":["gzip, deflate, br"],"Sec-Fetch-Mode":["navigate"],"X-Forwarded-Proto":["https"],"X-Forwarded-Host":["domain2.programster.org"],"Accept-Language":["en-GB,en;q=0.5"],"Upgrade-Insecure-Requests":["1"],"Sec-Gpc":["1"],"Sec-Fetch-Site":["none"],"Cache-Control":["no-cache"],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"],"Sec-Fetch-Dest":["document"],"Dnt":["1"],"Pragma":["no-cache"],"Te":["trailers"],"Sec-Fetch-User":["?1"],"X-Forwarded-For":["172.20.0.1"]},"tls":{"resumed":true,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"domain2.programster.org"}}}
demo  | {"level":"debug","ts":1712335895.170236,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":"127.0.0.1:9000","duration":0.00241281,"request":{"remote_ip":"172.20.0.1","remote_port":"37088","client_ip":"172.20.0.1","proto":"HTTP/2.0","method":"GET","host":"domain2.programster.org","uri":"/index.php","headers":{"X-Forwarded-For":["172.20.0.1"],"Te":["trailers"],"Sec-Fetch-User":["?1"],"Sec-Fetch-Mode":["navigate"],"X-Forwarded-Proto":["https"],"X-Forwarded-Host":["domain2.programster.org"],"Cookie":[],"Accept-Encoding":["gzip, deflate, br"],"Sec-Fetch-Site":["none"],"Cache-Control":["no-cache"],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"],"Accept-Language":["en-GB,en;q=0.5"],"Upgrade-Insecure-Requests":["1"],"Sec-Gpc":["1"],"Pragma":["no-cache"],"Sec-Fetch-Dest":["document"],"Dnt":["1"]},"tls":{"resumed":true,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"domain2.programster.org"}},"headers":{"Content-Type":["text/html; charset=UTF-8"]},"status":200}
demo  | {"level":"debug","ts":1712335895.1977758,"logger":"http.handlers.rewrite","msg":"rewrote request","request":{"remote_ip":"172.20.0.1","remote_port":"37088","client_ip":"172.20.0.1","proto":"HTTP/2.0","method":"GET","host":"domain2.programster.org","uri":"/favicon.ico","headers":{"Sec-Fetch-Mode":["no-cors"],"Dnt":["1"],"Pragma":["no-cache"],"Te":["trailers"],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0"],"Accept":["image/avif,image/webp,*/*"],"Sec-Fetch-Dest":["image"],"Sec-Fetch-Site":["same-origin"],"Cache-Control":["no-cache"],"Sec-Gpc":["1"],"Accept-Encoding":["gzip, deflate, br"],"Referer":["https://domain2.programster.org/"],"Cookie":[],"Accept-Language":["en-GB,en;q=0.5"]},"tls":{"resumed":true,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"domain2.programster.org"}},"method":"GET","uri":"/my-subfolder/favicon.ico"}
demo  | {"level":"debug","ts":1712335895.1978335,"logger":"http.handlers.rewrite","msg":"rewrote request","request":{"remote_ip":"172.20.0.1","remote_port":"37088","client_ip":"172.20.0.1","proto":"HTTP/2.0","method":"GET","host":"domain2.programster.org","uri":"/my-subfolder/favicon.ico","headers":{"Sec-Fetch-Site":["same-origin"],"Cache-Control":["no-cache"],"Accept-Encoding":["gzip, deflate, br"],"Referer":["https://domain2.programster.org/"],"Cookie":[],"Sec-Gpc":["1"],"Accept-Language":["en-GB,en;q=0.5"],"Dnt":["1"],"Pragma":["no-cache"],"Te":["trailers"],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0"],"Accept":["image/avif,image/webp,*/*"],"Sec-Fetch-Dest":["image"],"Sec-Fetch-Mode":["no-cors"]},"tls":{"resumed":true,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"domain2.programster.org"}},"method":"GET","uri":"/index.php"}
demo  | {"level":"debug","ts":1712335895.197854,"logger":"http.handlers.reverse_proxy","msg":"selected upstream","dial":"127.0.0.1:9000","total_upstreams":1}
demo  | {"level":"debug","ts":1712335895.1979272,"logger":"http.reverse_proxy.transport.fastcgi","msg":"roundtrip","request":{"remote_ip":"172.20.0.1","remote_port":"37088","client_ip":"172.20.0.1","proto":"HTTP/2.0","method":"GET","host":"domain2.programster.org","uri":"/index.php","headers":{"Accept":["image/avif,image/webp,*/*"],"Dnt":["1"],"Sec-Gpc":["1"],"Accept-Language":["en-GB,en;q=0.5"],"X-Forwarded-Proto":["https"],"Sec-Fetch-Site":["same-origin"],"Referer":["https://domain2.programster.org/"],"Sec-Fetch-Dest":["image"],"Sec-Fetch-Mode":["no-cors"],"Te":["trailers"],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0"],"Cookie":[],"Accept-Encoding":["gzip, deflate, br"],"Pragma":["no-cache"],"Cache-Control":["no-cache"],"X-Forwarded-For":["172.20.0.1"],"X-Forwarded-Host":["domain2.programster.org"]},"tls":{"resumed":true,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"domain2.programster.org"}},"env":{"REMOTE_USER":"","SERVER_NAME":"domain2.programster.org","REQUEST_URI":"/favicon.ico","SERVER_PORT":"443","HTTPS":"on","HTTP_TE":"trailers","HTTP_USER_AGENT":"Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0","HTTP_PRAGMA":"no-cache","AUTH_TYPE":"","GATEWAY_INTERFACE":"CGI/1.1","REMOTE_HOST":"172.20.0.1","DOCUMENT_ROOT":"/var/www/site/public_html","HTTP_HOST":"domain2.programster.org","HTTP_CACHE_CONTROL":"no-cache","HTTP_ACCEPT":"image/avif,image/webp,*/*","HTTP_DNT":"1","DOCUMENT_URI":"/index.php","SSL_PROTOCOL":"TLSv1.3","HTTP_SEC_FETCH_MODE":"no-cors","HTTP_REFERER":"https://domain2.programster.org/","SSL_CIPHER":"TLS_AES_128_GCM_SHA256","HTTP_ACCEPT_ENCODING":"gzip, deflate, br","HTTP_X_FORWARDED_FOR":"172.20.0.1","CONTENT_LENGTH":"","REMOTE_PORT":"37088","SERVER_PROTOCOL":"HTTP/2.0","SERVER_SOFTWARE":"Caddy/v2.7.6","SCRIPT_FILENAME":"/var/www/site/public_html/index.php","HTTP_X_FORWARDED_HOST":"domain2.programster.org","QUERY_STRING":"","HTTP_SEC_FETCH_DEST":"image","HTTP_COOKIE":"","CONTENT_TYPE":"","PATH_INFO":"","REQUEST_SCHEME":"https","HTTP_X_FORWARDED_PROTO":"https","HTTP_SEC_GPC":"1","HTTP_SEC_FETCH_SITE":"same-origin","REMOTE_IDENT":"","REMOTE_ADDR":"172.20.0.1","REQUEST_METHOD":"GET","SCRIPT_NAME":"/index.php","HTTP_ACCEPT_LANGUAGE":"en-GB,en;q=0.5"},"dial":"127.0.0.1:9000","env":{"SERVER_NAME":"domain2.programster.org","REQUEST_URI":"/favicon.ico","SERVER_PORT":"443","HTTPS":"on","REMOTE_USER":"","GATEWAY_INTERFACE":"CGI/1.1","REMOTE_HOST":"172.20.0.1","DOCUMENT_ROOT":"/var/www/site/public_html","HTTP_HOST":"domain2.programster.org","HTTP_TE":"trailers","HTTP_USER_AGENT":"Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0","HTTP_PRAGMA":"no-cache","AUTH_TYPE":"","HTTP_ACCEPT":"image/avif,image/webp,*/*","HTTP_DNT":"1","HTTP_CACHE_CONTROL":"no-cache","SSL_PROTOCOL":"TLSv1.3","HTTP_SEC_FETCH_MODE":"no-cors","HTTP_REFERER":"https://domain2.programster.org/","DOCUMENT_URI":"/index.php","REMOTE_PORT":"37088","SERVER_PROTOCOL":"HTTP/2.0","SERVER_SOFTWARE":"Caddy/v2.7.6","SCRIPT_FILENAME":"/var/www/site/public_html/index.php","SSL_CIPHER":"TLS_AES_128_GCM_SHA256","HTTP_ACCEPT_ENCODING":"gzip, deflate, br","HTTP_X_FORWARDED_FOR":"172.20.0.1","CONTENT_LENGTH":"","HTTP_X_FORWARDED_HOST":"domain2.programster.org","HTTP_SEC_FETCH_DEST":"image","HTTP_COOKIE":"","QUERY_STRING":"","PATH_INFO":"","REQUEST_SCHEME":"https","HTTP_X_FORWARDED_PROTO":"https","CONTENT_TYPE":"","HTTP_SEC_FETCH_SITE":"same-origin","HTTP_SEC_GPC":"1","REMOTE_ADDR":"172.20.0.1","REQUEST_METHOD":"GET","SCRIPT_NAME":"/index.php","HTTP_ACCEPT_LANGUAGE":"en-GB,en;q=0.5","REMOTE_IDENT":""},"request":{"remote_ip":"172.20.0.1","remote_port":"37088","client_ip":"172.20.0.1","proto":"HTTP/2.0","method":"GET","host":"domain2.programster.org","uri":"/index.php","headers":{"Cache-Control":["no-cache"],"X-Forwarded-For":["172.20.0.1"],"X-Forwarded-Host":["domain2.programster.org"],"Pragma":["no-cache"],"Dnt":["1"],"Sec-Gpc":["1"],"Accept-Language":["en-GB,en;q=0.5"],"X-Forwarded-Proto":["https"],"Accept":["image/avif,image/webp,*/*"],"Referer":["https://domain2.programster.org/"],"Sec-Fetch-Site":["same-origin"],"Sec-Fetch-Mode":["no-cors"],"Te":["trailers"],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0"],"Cookie":[],"Accept-Encoding":["gzip, deflate, br"],"Sec-Fetch-Dest":["image"]},"tls":{"resumed":true,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"domain2.programster.org"}}}
demo  | {"level":"debug","ts":1712335895.2011986,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":"127.0.0.1:9000","duration":0.003314738,"request":{"remote_ip":"172.20.0.1","remote_port":"37088","client_ip":"172.20.0.1","proto":"HTTP/2.0","method":"GET","host":"domain2.programster.org","uri":"/index.php","headers":{"Accept":["image/avif,image/webp,*/*"],"Dnt":["1"],"Sec-Gpc":["1"],"Accept-Language":["en-GB,en;q=0.5"],"X-Forwarded-Proto":["https"],"Sec-Fetch-Site":["same-origin"],"Referer":["https://domain2.programster.org/"],"Sec-Fetch-Dest":["image"],"Sec-Fetch-Mode":["no-cors"],"Te":["trailers"],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0"],"Cookie":[],"Accept-Encoding":["gzip, deflate, br"],"Pragma":["no-cache"],"Cache-Control":["no-cache"],"X-Forwarded-For":["172.20.0.1"],"X-Forwarded-Host":["domain2.programster.org"]},"tls":{"resumed":true,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"domain2.programster.org"}},"headers":{"Status":["404 Not Found"],"Content-Type":["text/html;charset=UTF-8"]},"status":404}

3. Caddy version:

I’m running Caddy v2.7.6 h1:w0NymbG2m9PcvKWsrXO6EEkY9Ru4FJK8uQbYcev1p3A=

4. How I installed and ran Caddy:

I installed Caddy in a Docker container on Debian 12 with the following beginning of my Dockerfile:

FROM debian:12

RUN apt-get update && apt-get dist-upgrade -y

# Install the relevant packages
RUN apt-get install -y php-fpm curl php8.2-curl php8.2-xml php8.2-mbstring php8.2-bcmath git unzip php8.2-zip \
    supervisor cron curl composer vim

# Install caddy webserver
RUN apt-get install -y debian-keyring debian-archive-keyring apt-transport-https curl -y \
  && curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg \
  && curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list \
  && apt-get update \
  && apt-get install caddy -y

It runs caddy on startup as the foreground process with:

/usr/bin/caddy run --config=/etc/caddy/Caddyfile

b. Command:

No commands manually run

c. Service/unit/compose file:

I’m running caddy inside my app, rather than as a separate container, but just in-case you need my docker-compose.yaml file:

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: demo
    image: demo
    restart: unless-stopped
    volumes:
      - ./site:/var/www/site
      - ./ssl:/ssl
    ports:
      - "80:80"
      - "443:443"

d. My complete Caddy config:

# global options
{
    debug

    # Disable the automatic management of TLS certs with Let's Encrypt
    # https://caddyserver.com/docs/caddyfile/options#auto-https
    auto_https disable_certs
}


*.programster.org {
    tls /ssl/fullchain.pem /ssl/privkey.pem

    @foo host domain2.programster.org
    handle @foo {
        root * /var/www/site/public_html
        encode gzip
        #rewrite * /my-subfolder/{uri}
        uri path_regexp .* /my-subfolder{uri}
        php_fastcgi 127.0.0.1:9000
        file_server
    }

    @bar host domain1.programster.org
    handle @bar {
        root * /var/www/site/public_html
        encode gzip
        php_fastcgi 127.0.0.1:9000
        file_server
    }
}

In most frameworks, you can route based on the hostname. That’s the preferred approach. Much simpler, hands control of routing to your app instead of hacking it in the webserver.

But it seems like Slim declined to add that functionality. I think that’s silly Grouping routes by domains · Issue #2618 · slimphp/Slim · GitHub

The problem with trying to do rewrites like this is the FastCGI spec says that REQUEST_URI must be the original request path, unmodified. That means that rewrites must not affect that env var which is passed to PHP, which PHP uses for routing the request. It’s possible to override that behaviour with the env option in php_fastcgi, but I don’t recommend it, that’s the wrong solution.

Really, you should either consider switching frameworks (hopefully this is still early in your project and wouldn’t be too difficult), or split it up in two apps (either Slim\App instances in code, or literally two separate projects in separate directories).

1 Like

Thank you for such a comprehensive response and so quickly!

I took your advice and have rejigged how my Slim app get’s created so that I could workaround that it doesn’t have native support for domain based routing. It’s worked so far for my demo codebase without issues:

<?php

use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;
use Slim\Routing\RouteCollectorProxy;

require_once(__DIR__ . '/../bootstrap.php');

$app = AppFactory::create();
$app->addRoutingMiddleware();
$app->addErrorMiddleware(true, true, true);

// Set the routes that should be under / on the secondary domain, or under /my-subfolder on the primary domain.
$secondSiteRoutes = function (RouteCollectorProxy $app) {
    $app->get('', function (Request $request, Response $response, $args) {
        $body = $response->getBody();
        $body->write('On the sub-site page...');
        $newResponse = $response->withBody($body);
        return $newResponse;
    });
};

$host = $_SERVER['HTTP_HOST'];
if (str_contains($host, "domain2.programster.org"))
{
    // User hit secondary domain, only add the second domain's routes, under /
    $app->group('/', $secondSiteRoutes);
}
else
{
    // Define the primary domain routes.
    $app->get('/', function (Request $request, Response $response, $args) {
        $body = $response->getBody();
        $body->write('On the main page...'); // returns number of bytes written
        $newResponse = $response->withBody($body);
        return $newResponse;
    });

    // add the second domain routes under a subfolder.
    $app->group('/my-subfolder', $secondSiteRoutes);
}

// Run app
$app->run();
1 Like

That seems like a sensible way to do it :+1:

1 Like