Setup CORS in Caddy 2

Caddy 2 is an open source web server with automatic HTTPS. It's a wise choice for pet projects or self-hosted services, since you are free from managing TLS certs on your own and wiring things together can be super annoying.

One missing feature in Caddy 2 is CORS support. For a "batteries included" web server, it's rather surprising. Fortunately, one can use the following Caddy snippet to augment any site with CORS headers without repeating oneself over and over again.

Please note, you might want to update the list of headers returned by Access-Control-Allow-Headers and Access-Control-Expose-Headers HTTP headers according to your application needs. Please refer to the CORS documentation to learn more what they mean.

(cors) {
  @cors_preflight method OPTIONS
  @cors header Origin {args.0}

  handle @cors_preflight {
    header Access-Control-Allow-Origin "{args.0}"
    header Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE"
    header Access-Control-Allow-Headers "Content-Type"
    header Access-Control-Max-Age "3600"
    respond "" 204
  }

  handle @cors {
    header Access-Control-Allow-Origin "{args.0}"
    header Access-Control-Expose-Headers "Link"
  }
}

example.com {
  import cors https://example.com
  reverse_proxy localhost:8080
}

The nice part of this snippet is that CORS headers are only returned for the requests with Origin HTTP request header. That header is normally used only by browsers, which means your regular curl requests or requests made via your programming-language-of-choice won't see them.

I've been successfully using this snippet for quite a while now to protect xsnippet-api served at api.xsnippet.org, so it can be used by xsnippet-web served at xsnippet.org.