Reverse proxy + static file serving results in 403 (Forbidden) for static files

1. Caddy version (caddy version):

v2.4.6 h1:HGkGICFGvyrodcqOOclHKfvJC0qTU7vny/7FhYp9hNw=

2. How I run Caddy:


ExecStart=/usr/bin/caddy run --environ --config /etc/caddy/Caddyfile
ExecReload=/usr/bin/caddy reload --config /etc/caddy/Caddyfile


a. System environment:

Distributor ID: Ubuntu
Description:    Ubuntu 20.04.4 LTS
Release:        20.04
Codename:       focal

b. Command:

sudo systemctl start caddy

c. Service/unit/compose file:


d. My complete Caddyfile or JSON config:


# Mirage, PostgreSQL {
  handle /uploads/* {
    root * /home/inhji/www/mirage/data/uploads

  handle {
    reverse_proxy * localhost:9100

# Miniflux, PostgreSQL {
  reverse_proxy localhost:9010

# Linkding, Docker, SQLite {
  reverse_proxy localhost:9020

# Vaultwarden, Docker, SQLite {
  reverse_proxy localhost:9030

3. The problem Iā€™m having:

I have a webapp running unter localhost:9100 which is proxied by caddy to I also have a directory under /home/inhji/www/mirage/data from where I want to serve static files.

But Iā€™m not sure how to configure caddy to do this.

4. Error messages and/or full log output:

When I try to load an image, it gives me a 403 Error:

--2022-03-24 12:45:13--
Resolving (
Connecting to (||:443... connected.
HTTP request sent, awaiting response... 403 Forbidden
2022-03-24 12:45:13 ERROR 403: Forbidden.

This is what caddy shows in the log for the file:

{"level":"debug","ts":1648127021.376651,"logger":"http.handlers.file_server","msg":"sanitized path join","site_root":"/home/inhji/www/mirage/data/uploads","request_path":"/uploads/avatar/avatar-square.png","result":"/home/inhji/www/mirage/data/uploads/uploads/avatar/avatar-square.png"}

When checking the path /home/inhji/www/mirage/data/uploads/uploads/avatar/avatar-square.png, it exists:

stat /home/inhji/www/mirage/data/uploads/uploads/avatar/avatar-square.png
  File: /home/inhji/www/mirage/data/uploads/uploads/avatar/avatar-square.png
  Size: 72784           Blocks: 144        IO Block: 4096   regular file
Device: 801h/2049d      Inode: 392593      Links: 1
Access: (0777/-rwxrwxrwx)  Uid: ( 1000/   inhji)   Gid: ( 1000/   inhji)
Access: 2022-03-24 12:39:57.204641311 +0000
Modify: 2022-03-24 08:04:49.084178736 +0000
Change: 2022-03-24 12:21:29.002193445 +0000
 Birth: -

5. What I already tried:

I have tried different configuration of the /uploads block: {
    root * /home/inhji/www/mirage/data/uploads
} {
    reverse_proxy * localhost:9100

I also checked the permissions of the folder in question and changed the permissions to 777 and the owner to caddy:

 ls -la /home/inhji/www/mirage/data/uploads/uploads/avatar
total 128
drwxrwxrwx 2 caddy caddy  4096 Mar 23 21:21 .
drwxrwxrwx 4 caddy caddy  4096 Mar 23 21:21 ..
-rwxrwxrwx 1 caddy caddy 15673 Mar 24 08:04 avatar-original.jpeg
-rwxrwxrwx 1 caddy caddy 29691 Mar 24 07:40 avatar-original.jpg
-rwxrwxrwx 1 caddy caddy 72784 Mar 24 08:04 avatar-square.png



  • The path of my static files does contain the segment ../uploads/uploads/.., this is not an error.
1 Like

Hi :slight_smile:
Thanks for providing so much info!

The problem here seems to be (Linux) file permissions.
While you did set 0777 (u=rwx,g=rwx,o=rwx) on the files you want to access, the caddy user still has to have execution access for every parent folder in the path to traverse/reach the file.

This is nothing caddy can change - this is just how Linux file permissions work.

Pick one:

  • So you could either use a totally different folder like /srv or /var/www (recommended :white_check_mark:)
  • OR add execution perms for literally any user on the inhji home directory via
    • chmod a+x /home/inhji
    • chmod a+x /home/inhji/www
    • chmod a+x /home/inhji/www/mirage
    • chmod a+x /home/inhji/www/mirage/data
    • chmod a+x /home/inhji/www/mirage/data/uploads and finally
    • chmod -R a+x /home/inhji/www/mirage/data/uploads to recursively go over all potential subdirs in data/uploads.
      I strongly advise against running chmod -R a+x /home/inhji because this will change EVERY SUBDIRECTORY in /home/inhji
  • OR add exec perms only for a group caddy is part of and set on the folders via chown on the inhji home directory via the same sequence above but this time using chmod g+x instead of o+x

The way file_server works, it takes the defined root and appends the current request path to it, to find the file.

So this means for a request like /uploads/file.txt, it would assemble the path as /home/inhji/www/mirage/data/uploads/uploads/file.txt.

Notice /uploads is in there twice.

To fix this, use handle_path instead of handle, which will strip the matched path prefix before running the handlers within.


Thanks James,

I moved my files to /var/www now, after verifying that indeed changing the permissions of all parentfolders would have worked, too.

Here is my final config block for reference, which also incorporates @francislavoie 's suggestion: {
  handle_path /uploads/* {
    root * /var/www/mirage/uploads

  handle {
    reverse_proxy * localhost:9100

1 Like

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