Serve and route requests

Serve a static site

Set a document root, serve files, and verify that filesystem ownership matches the Caddy process.

Serving files is the oldest job a web server has. In Caddy it takes two directives.

root sets the directory that request paths are resolved against. file_server does the actual work. It takes the request path, finds the matching file under the root, and sends it back.

:8080 {
  root * ./public
  file_server
}

The * after root is a matcher. It means “every request”. We’ll use more precise matchers in the next lesson.

Create some content and start the server:

mkdir -p public
echo '<h1>My site</h1>' > public/index.html
caddy run --config Caddyfile

Now let’s verify both a hit and a miss:

curl http://127.0.0.1:8080/
curl -i http://127.0.0.1:8080/missing.html

The first request returns your HTML. file_server serves index.html for directory requests without you asking. The second returns 404 Not Found, and that’s what you want. No hints about what else exists on disk.

Why two directives?

You might ask why root isn’t just an option of file_server. Because other handlers use the root too. try_files, php_fastcgi, and rewrites all resolve paths against it. Set it once and every handler points at the same tree.

Caddy protects you from path traversal, the trick of using .. in a URL to climb out of the root. A request for /../../etc/passwd cannot escape ./public.

But Caddy follows symbolic links. A symlink inside ./public that points at /etc will be served like any other folder. Before you serve a real directory, check what’s in it:

find ./public -type l

An empty result means no symlinks. If something shows up, make sure you meant it.

The classic production failure

On a real server, the most common failure is permissions. The packaged service runs as the caddy user. Home directories are often mode 700, which means only the owner can read them.

So your files exist, your config is valid, and every request still fails. Check the runtime log first:

journalctl -u caddy

The error names the file it couldn’t read. Fix it by giving the caddy user read access to the site tree, and nothing more. Don’t make the tree world-writable out of frustration. That trades a visible error for an invisible security hole.

Lesson completed