Caddyfile for Django API and SPA

1. Caddy version (caddy version):

v2.1.1

2. How I run Caddy:

Caddy is run as a system unit.

a. System environment:

Ubuntu 20.04

b. Command:

./caddy validate Caddyfile
sudo systemctl restart caddy

d. My complete Caddyfile or JSON config:

mydomain.com {
    log {
        output stdout
        level DEBUG
    }
    reverse_proxy /admin* 127.0.0.1:8000
    reverse_proxy /api* 127.0.0.1:8000

    file_server /static/* {
        root /path/to/back
    }

    file_server /media/* {
        root /path/to/back
    }

    try_files {path} /index.html
    file_server /* {
        root /path/to/front/dist
    }
}

3. The problem I’m having:

I’m serving both a Django API and a SPA. I’m trying to use try_files directive (similar to what I used in Nginx) but failing at finding the proper configuracion.

5. What I already tried:

Initially I had this configuration:

...
    file_server /* {
        root /path/to/front/dist
    }

which works ok for mydomain.com but cannot load mydomain.com/team. So I’m trying to change it to serve the SPA correctly.

I’ve tried other modifications (apart from the shown in the d section) in the Caddyfile like:

try_files {path} /index.html
root /* /path/to/front/dist

or

try_files {path} /index.html
root /* /path/to/front/dist
file_server

The configuration seems fine (it validates), but I cannot reach mydomain.com/team which is expected.

Thank you for your help!

Me again! After pairing with a colleague, we found this answer which was helpful. I’ll let here the final configuration that works:

mydomain.com {
    log {
        output stdout
        level DEBUG
    }
    
    root /static/* /path/to/back
    root /media/* /path/to/back
    root * /path/to/front/dist
    
    route {
        reverse_proxy /admin* 127.0.0.1:8000
        reverse_proxy /api* 127.0.0.1:8000
        try_files {path} {path}/ /index.html
        file_server
    }
}

Glad you figured it out!

FYI, it’s very slightly more efficient to use root * /path/to/front/dist rather than /* because /* is a path matcher which will always match, whereas * will just match every request without doing a path comparison.

Thanks for your tip! I’ll add it to the solution, should anyone copy and paste it.