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:

[Unit]
Description=Caddy
Documentation=https://caddyserver.com/docs/
After=network.target network-online.target
Requires=network-online.target

[Service]
Type=notify
User=caddy
Group=caddy
ExecStart=/usr/bin/caddy run --environ --config /etc/caddy/Caddyfile
ExecReload=/usr/bin/caddy reload --config /etc/caddy/Caddyfile
TimeoutStopSec=5s
LimitNOFILE=1048576
LimitNPROC=512
PrivateTmp=true
ProtectSystem=full
AmbientCapabilities=CAP_NET_BIND_SERVICE

[Install]
WantedBy=multi-user.target

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:

none

d. My complete Caddyfile or JSON config:

{
  email my@email.com
  debug
}

# Mirage, PostgreSQL
inhji.de {
  handle /uploads/* {
    root * /home/inhji/www/mirage/data/uploads
    file_server
  }

  handle {
    reverse_proxy * localhost:9100
  }
}

# Miniflux, PostgreSQL
rss.inhji.de {
  reverse_proxy localhost:9010
}

# Linkding, Docker, SQLite
link.inhji.de {
  reverse_proxy localhost:9020
}

# Vaultwarden, Docker, SQLite
pw.inhji.de {
  reverse_proxy localhost:9030
}

3. The problem I’m having:

I have a webapp running unter localhost:9100 which is proxied by caddy to inhji.de. 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:

 wget https://inhji.de/uploads/avatar/avatar-square.png
--2022-03-24 12:45:13--  https://inhji.de/uploads/avatar/avatar-square.png
Resolving inhji.de (inhji.de)... 49.12.68.17
Connecting to inhji.de (inhji.de)|49.12.68.17|: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:

inhji.de/uploads {
    root * /home/inhji/www/mirage/data/uploads
    file_server
}

inhji.de {
    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

Thanks!

Remarks

  • 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
4 Likes

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.

2 Likes

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:

inhji.de {
  handle_path /uploads/* {
    root * /var/www/mirage/uploads
    file_server
  }

  handle {
    reverse_proxy * localhost:9100
  }
}

1 Like

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