Serving multiple static sites on a single caddy instance

1. The problem I’m having:

I am currently running a caddy instance as my webserver, behind another caddy instance that acts as a reverse proxy (see my previous post). I’d like to add another static site to this and use the reverse proxy to route to the different sites. I was wondering if I could host both sites on the same port and use the caddy webserver to route to the correct one, depending on which domain got routed to the webserver or if I need to host each site on a different port.

3. Caddy version:

I’m using caddy 2.10.2 within docker

4. How I installed and ran Caddy:

a. System environment:

I’m running caddy in docker compose, using the recommended docker compose config.

b. Command:

I’m running through docker without extra commands

c. Service/unit/compose file:

services:
  caddy:
    image: caddy:2.10.0
    restart: unless-stopped
    ports:
      - 80:80
      - 443:443
      - 443:443/udp
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - ./caddy/data:/data
      - ./caddy/config:/config
      - ./docs/site:/var/www/mysite1
      - ./notes/site:/var/www/mysite2

d. My complete Caddy config:

If I need to host every site on a different port, I’d assume my caddy file would look like this:

192.168.5.164:80 {
    root * /var/www/mysite1
}

192.168.5164:81 {
    root * /var/www/mysite2
}

But I’d like to host both on a single port if that’s possible, to prevent the amount of ports open. I read that I can use headers to server different content, but could I also use the domain that is requesting the content? My reverse proxy caddyfile would look something like this with the different port setup:

docs.domain.tld {
    reverse_proxy 192.168.5.164:80 {
        header_up Host {upstream_hostport}
    }
}

notes.domain.tld {
    reverse_proxy 192.168.5.164:81 {
        header_up Host {upstream_hostport}
    }
}

Is there any way to use the domain that the reverse proxy uses to forward the request to server different content?

5. Links to relevant resources:

I’m having a hard time understanding the question. Can you phrase it differently?

I have two static sites, which I both want to serve on a different domain (e.g. site1.domain.com goes to site1 and site2.domain.com goes to site2).

I have a caddy webserver on a separate webserver machine that serves both of these sites, currently on two different ports.

I have a different machine that runs caddy reverse proxy for these sites, which proxies the domain names to the different webserver ports.

I was wondering if I could minimize the amount of ports open on the webserver by serving both sites on port 80 (on different paths or something). And still serve each site depending on the url that the caddy reverse proxy uses.

Maybe the current way is the correct way of serving these things, which would also be fine, but I’d like to know what the best way is.

I’m typing on the phone, so excuse some of the typos and the bad formatting. You can do something like this.

site-1.example.com, site-2.example.com {
    reverse_proxy {labels.1}:80
}

On the other server, you may do something like this

:80 {
    root /srv/www/{host}
    file_server
}

Where the /srv/www has 2 directories, one named site-1 and the other is named site-2.

1 Like

What does the labels.1 do in this case?

Just to expand on what Mohammed shared - I believe he may have meant {labels.2} instead of {labels.1}. To answer your question about what the labels do in this case, these two formats are effectively equivalent:

This format:

site-1.example.com, site-2.example.com {
    reverse_proxy {labels.2}:80
}

is a more elegant way of writing this:

site-1.example.com {
    reverse_proxy site-1:80
}

site-2.example.com {
    reverse_proxy site-2:80
}

Then, on your reverse proxy server, you can add a hosts file entry like:

192.168.5.164  site-1 site-2

Or, if you want to keep everything within Caddy, you can do this:

site-1.example.com, site-2.example.com {
    reverse_proxy 192.168.5.164:80 {
        header_up Host {labels.2}
    }
}

Request host labels (0-based from right); e.g. for foo.example.com: 0=com, 1=example, 2=foo

1 Like

Sounds awesome! I’m going to try this out. Thanks for the suggestions.

I’ve tried using the solutions posted here, but it seems that the webserver is misbehaving. When using curl to get the domain name, I get a correct 200, but there isn’t anything served by the webserver. When I use curl -v ``http://192.168.5.164:80`` -H “Host: notes” (please note if that’s wrong), I get the following output:

*   Trying 192.168.5.164:80...
* Connected to 192.168.5.164 (192.168.5.164) port 80
> GET / HTTP/1.1
> Host: notes
> User-Agent: curl/8.7.1
> Accept: */*
> 
* Request completely sent off
< HTTP/1.1 200 OK
< Server: Caddy
< Date: Sun, 09 Nov 2025 11:30:01 GMT
< Content-Length: 0
< 
* Connection #0 to host 192.168.5.164 left intact

It seems like everything is functional, but the files aren’t served. The files are bind-mounted in the docker compose:

services:
  caddy:
    image: caddy:2.10.2
    restart: unless-stopped
    ports:
      - 80:80
      - 443:443
      - 443:443/udp
      - 8080-8085:8080-8085
      
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - $DATA_STORE/caddy/data:/data
      - $DATA_STORE/caddy/config:/config
      - $REPO_STORE/docs/site:/srv/docs
      - $REPO_STORE/notes/public:/srv/notes

And the Caddyfile for the webserver is serving them:

192.168.5.164:80 {
    root * /srv/{host}
    file_server
}

I don’t really get what isn’t working.

EDIT: I’ve re-added the earlier header_up Host {upstream_hostport} to the reverse proxy and this has changed the files to a 404: Not Found error. This is weird, because the directories do exist within the container and have index.html files in them. I’ve checked this with docker exec -it sh. This is de debug message I get:

{"level":"debug","ts":1762688971.930795,"logger":"http.log.error","msg":"{id=zd2yxqz21} fileserver.(*FileServer).notFound (staticfiles.go:721): HTTP 404","request":{"remote_ip":"192.168.2.125","remote_port":"50179","client_ip":"192.168.2.125","proto":"HTTP/1.1","method":"GET","host":"192.168.5.164","uri":"/","headers":{"User-Agent":["curl/8.7.1"],"Accept":["*/*"]}},"duration":0.000089348,"status":404,"err_id":"zd2yxqz21","err_trace":"fileserver.(*FileServer).notFound (staticfiles.go:721)"}

EDIT 2: It seems like I can’t really follow instructions correctly, something went wrong, causing me to re-add the 192.168.5.164:80 to the router, which caused the server to try and serve /srv/192.168.5.164. I removed that part and it was fully functional. Thanks for all the help and I’m sorry for not following the instructions correctly.