The "subfolder problem", OR, "why can't I reverse proxy my app into a subfolder?"

Have you ever tried to reverse proxy an app into its own little subfolder of your domain name?

Makes things neat, doesn’t it? Using example.com/foo/ for one app, example.com/bar/ for another. If you’re coming here from one of the selfhosted communities, you might be thinking along the lines of example.com/sonarr/, example.com/radarr/ etc.

Chances are, you’ve tried some configuration along these lines:

example.com {
  redir /sonarr /sonarr/
  handle_path /sonarr/* {
    reverse_proxy localhost:8989
  }
}

But then disaster strikes, and you get a very poorly formatted page! Maybe links don’t work at all. Maybe you just get redirected straight away to a blank page. Images, style sheets, and scripts don’t work. If you’re struggling with this issue, read on!

Understanding the problem


Firstly, know that this isn’t something unique to Caddy. Every single reverse proxy server you’d care to name will have this inherent problem (unless they have some seriously advanced logic built in to handle it!). That’s because this is a logical problem, not strictly a bug or issue with Caddy or the app itself.

Almost all HTTP applications are written under the assumption that they are serving requests from the web root. A request to example.com/ would be a request for the web root - it just means the highest point in the path hierarchy, the first path element, i.e. not a subfolder.

When you request a website, you usually get a HTML document in response. The HTML directs your browser to additional elements; static assets like images, style sheets, and scripts it uses to further render out the web page. Because it’s assuming it has the web root, it might give your browser links like /images/stock.jpg and /css/site.css. When you open your browser and access your application directly, that’s fine.

But when we put a reverse proxy in the middle, and pen it into a subfolder, we might run into a problem. You’re proxying your application from /foo/. When you send the request to Caddy, and you’re browsing to example.com/foo/, you get a HTML document just like before. But the document still has links to /images/stock.jpg and /css/site.css. Your browser follows those links and makes those requests to Caddy. But Caddy doesn’t know how to handle requests for /images/ or /css/! You’ve only configured the reverse proxy for /foo/. And now our asset links are broken and the page is, too.

Redirects are another problem, as well. They don’t happen in HTML, usually; they happen in HTTP headers (specifically, Location). These, too, are unlikely to properly reference the subfolder you want.

Fixing the problem


Ultimately, to fix this issue, all links and redirects must match up to valid URLs that Caddy knows how to handle. Something’s gotta give; either the HTTP application must correct its URLs to add the subfolder you want, or you need to configure Caddy to let the app have the web root it wants.

Option 1: Upstream

Some apps, like Sonarr, are programmed with the assumption that they might be reverse proxied into a subfolder. There is a helpful option in the application’s settings - commonly referred to as a “URL Base” - which will add your subfolder as a prefix to all of its links/headers. If the app you’re proxying has a feature like this, hurrah! Set it and forget it. If not, this solution isn’t an option.

Option 2: In Caddy

A. By giving up the web root

You can use a subdomain instead, e.g. sonarr.example.com, and proxy the entire web root. This configuration precludes the issue described above and will work for absolutely any HTTP application.

B. By HTML filtering/header rewriting (advanced)

Alternately, you could try to use Caddy as a filtering layer and intercept those incorrect links as they come downstream from your HTTP application.

A Caddy 2 module has been completed and is in the process of being published that can perform replacements in response bodies: GitHub - caddyserver/replace-response: Caddy module that performs replacements in response bodies · GitHub

Using that plugin involves defining rules that Caddy uses to alter the HTML document it receives from the upstream HTTP application before sending it to the client. In this way, the client would receive the correct links in HTML.

Rewriting headers from upstream can be achieved already using Caddy’s reverse_proxy directive which changes headers only to or from the backend (or, to change any/all headers, you can use the header directive). In this way, you can fix Location redirects as well. Combined, if carefully executed, these two strategies can produce a neatly contained HTTP application inside a subfolder without requiring the app to explicitly support this behaviour.

28 Likes
Radarr/sonarr etc.. without base url
URI rewrite needed
Create a reverse proxy for two ports
Can't get redirects to work via Caddyfile(more info in description)
Caddy 2: KiwiBNC websocket reverse_proxy
"Classic" subfolder issues? (I read the wiki)
Handle_path does not append prefix to in app urls
Can't make reverse_proxy to play nicely with redirects (minio, netdata, urbit)
Reverse_proxy without base_url / subpath / prefix
[Solved] phpMyAdmin as subdirectory of localhost
Redirect port to subdir
Help reverse proxying to a subpath
Rewrite for heimdall dashboard
Migrate multiple sites w/ reverse proxy in v2 (proxy to subdir)
How to make reverse_proxy work properly
Caddy as reverse proxy for docker services
Airflow with docker compose in specific path
Accessing page using the IP:PORT of the server and preventing redirection to domain.tld/subfolder when accessed by IP
Reverse proxy with IP cameras question
Caddy reverse_proxy behind caddy reverse_proxy?
Single uri redirection vs subdomain for separate base directory
Can not redirect to service properly
How to get The reverse_proxy directive to strip the path of the request before forwarding the request upstream
Reverse proxy seems to have a sub path bug
Proxying IP address with path
Handle_path not working as expected
Reverse_proxy with custom header_up
Combining rewrite and reverse_proxy
How is subfolder proxying progressing?
Can't get Caddy v2 to work on Home Assistant as reverse proxy
CFSSL behind caddy reverse proxy with subpath rewrite
Reverse Proxy Ghost blog on Subpath breaking jquery
Plain reverse_proxy works - fails when using handle_path
Caddyfile config for reverse proxy using domain subfolder
[Docker] Reverse-proxy in path for a React containerized app
Webpage not rendering fully
On-demand Subdomains with HTTPS Failing
How to Serve Laravel Application in handle or handle_path directives
How do I set up multiple ports on localhost to a single backend?
Invalid response while proxying
Reverse proxy to docker daemon api: "Client sent an HTTP request to an HTTPS server"
Help doing a backend rebuild with subpath instead of subdomains
Reverse Proxy is not working
Reverse proxy not working as expected. 1
What's the optimal way to serve multiple static sites via Caddy Docker?
Multiple reverse proxy redirect to first site running on port 80
When use handle_path and file_server to deploy a react program , it doesn't work
Reverse proxy virtual directories to ports
Subfolder Redirection
Trying to setup reverse proxy for two services in Ubuntu
Accessing HTTP-only upstream app on a HSTS domain through Caddy
Reverse proxy to specific path
Server an external site on a subpath
How to use file_server with multiple SPAs?
Reverse Proxy Config Getting ERR_SSL_PROTOCOL_ERROR
Use URI for reverse proxy
Subdomain handling in URI
Can't access reverse proxy remotely
Reverse Proxying for Services running on Proxmox Hypervisor
Caddy with Symfony put under a symlinked subdirectory
Reverse proxy for subpath doesn't work when redirected to docker container
Reverse Proxy into subfolder by HTML filtering/header rewriting
Reverse Proxy into subfolder by HTML filtering/header rewriting
Multiple containers, same application, behind a caddy reverse-proxy routing to container 1 or 2 in function of the path
Reverse proxy to Xen-Orchestra Docker container not working as expected
Question on reverse proxying and general feedback on configuration
Something wrong in caddyfile
Subdir reverse_proxy fails while subdomain reverse_proxy works perfectly
Caddyfile for code-server
Overseerr Reverse Proxy Setup
Routing directive, Reverse Proxy assets got 400
Reverse Proxy and HTTPS for multiple services over Tailscale
qBittorrent and Caddy reverse proxy
Can't access resources which are served as a reverse proxy
Reverse proxy not working as expected
How can i use caddy on other port
Cant run Mern app under costum path, only index path works
Log the request before reverse_proxy
Combine multiple webinterfaces to 1 by caddy
Reverse Proxy routing based on directory in URL
Redirection with revers proxy
Caddyfile config for reverse proxy using domain subfolder
Reverse proxy localhost from docker
[SOLVED]Help to "reverse_proxying" two syncthing instances
Simple Caddyfile example map microservice name to port
Overseerr and caddy possible?
Running php-app in a subdirectory causes problems
Help with tomcat reverse proxy
How to route a separate app through the same domain using different path
Asrock ipmi and caddy v2
Https subpath routing issues on azure container instance (docker)
Caddyfile help - Removing subdirectory / subfolder from url
Caddyfile help - Removing subdirectory / subfolder from url
Reverse_proxy header_down on location header... or something equivalent?
Blank Page with reverse proxy
Can not reach the Main Page of my MediaWiki that's behind a reverse proxy server (Caddy). I can only see the Apache2 Default Page
Reverse Proxy with rewrite of path, stripping parts
Caddyfile + docker-compose + https
Help converting nginx config to caddy
Help converting nginx config to caddy
CSS stylesheet not being loaded with reverse proxy and uri strip_prefix
Can't access sub-paths in reverse-proxy
URL PATH Rewrite
Caddy confusion (concern)
Reverse proxy with path matchers
Having trouble getting redirect to work
Different redirection
Websocket Proxy (Phoscon App) - Caddy as NGINX alternative
404 when load css/js use reverse_proxy
General Security concerns. what to do next?
Can I route on remote_ip? or if remote_ip -eq
Full Caddyfile for home server
Reverse proxy not working for Sonarr
How to serve file_server from different path?
Rutorrent + reverse proxy
Change protocol from HTTP/2 to HTTP/1.1?
Jellyfin, Gotify, cadyv2
Port Forwarding for AdGuard Home (Port 80)
React UI that authenticates with Okta and talks over a websocket to a Java backend
.htaccess to caddy v2
Path Matcher not Working on Internal Network Address accessed Directly with IP
Errors with v2's reverse_proxy to remote domain
Map a subdomain to a subfolder
Reverse proxy via paths only shows a white page (but the title works)
How to make a reverse proxy on a specific route properly?
Handle and handle_path
Config for reverse proxy
Reverse proxy for LAN pc's as well as local host
Dont know why i get 502
Infinite redirect to sonarr/radarr
Trying to run localhost https with mutiple sites
How to proxy web applications with basepath
Problem with static files when reverse proyxing a webapp running in docker
Kavita behind Caddy Reverse Proxy
HTTPS for Docker Containers with Caddy and Tailscale
How can I reverse_proxy multiple ports to same domain?
Caddy in docker blocked accessing other docker containers
Caddy in docker blocked accessing other docker containers
Caddy in docker blocked accessing other docker containers
Rewrite path to index.php if no file or directory exists
Caddy as reverse proxy for git to github.com
Help with Caddy Reverse Proxy Path Issue for WordPress Dashboard Under Subdirectory
In caddy(+cloudflare) how to do proxy pass to multiple containers
How to use route reverse proxy absolute path static assets?
Automatically rewriting/redirecting with trailing slash
How to configure Caddy v2 to use a different port based on subpath
Wildcard subdomain, acme failing
Messed up: Need suggestions for recovery
Caddy & Tailscale - reverse proxy issues with immich, teslamate and grafana
Routing Issue: Blank Page When Routing Ruby on Rails Admin Section to "/admin" Route
Caddyfile configuration for reverse proxies - different behaviours
Multi Domain Application for NextJS and Caddy Server
MinIO Reverse Proxy - Template from Nginx
Reverse proxy multiple webpaths to a single domain
Reverse proxy from the path to the desired address
How to setup reverse proxy with path for GenAI Text Gen and SD gradio webui?
HTTPS not enabled on reverse-proxied Jellyfin
Unable to reverse proxy traffic from Caddy (Docker) to Host Machine (Code-Server as Systemd)
Unable to perform a ProxyPass
Cannot reach my home server services with reverse proxy and Handle_Path
Use multiple server management tools on the same domain
Rewrite local service's static url to subpath

A post was split to a new topic: Alias directive like in NGINX?

A post was split to a new topic: Treat a domain and subfolder as one single domain for upstream

the subfolder mentioned by you actually concerns the Context Path of a backend web server. As long as all your backend web servers have unique Context-Path respectively, a reverse proxy such as Caddy, F5 BigIP has no problem to support context-path based routing.