Open your terminal and run curl kamal.io
. You should see a JSON "résumé." Browse to kamal.io in your browser however, and you'll get a regular site! What's going on?
How
When a request is sent to kamal.io, the webserver, Caddy treats the curl
client differently. It does so by checking the User-agent
header and processing the request like so:
- if the
User-agent
begins withcurl
and the request path is/
:- rewrite the request internally to
/about.json
- rewrite the request internally to
- otherwise, serve the request as normal
This is what the relevant parts of my Caddy config look like:
rewrite {
if {path} is "/"
if {>User-agent} starts_with curl
to /about.json
}
But
This works, but there's one thing we need to take care of. Caddy configures a forced HTTP → HTTPS redirect by default. curl
doesn't follow redirect headers unless the -L
flag is passed. So, if you run curl kamal.io
with only the config above, it won't work.
~ ❯❯❯ curl kamal.io
<a href="https://kamal.io/">Moved Permanently</a>.
This works, however:
~ ❯❯❯ curl -L kamal.io
{
"name": "Kamal Nasser",
...
What Then
To fix this issue, we need to configure Caddy to not redirect HTTP curl requests to /
to HTTPS. To do so, you will need to add an HTTP server block, like so:
http://kamal.io/ {
...
}
This will let you override the default HTTP → HTTPS redirect and configure HTTP requests however you like. This matches all requests under /
(e.g. /index.html
), not only /
itself.
Similar to the rewrite
directive above, we will need to add a redir
directive that redirects only if the request isn't from a curl
client and isn't exactly /
.
http://kamal.io/ {
redir 301 {
if {path} not "/"
if {>User-agent} not_starts_with "curl"
if_op or
/ https://{host}{uri}
}
... webroot config ...
}
Now, it will be possible to run curl kamal.io
as-is and get a proper response and get an HTTPS redirect for every other kind of request.
But I'm using nginx. Can I still do this?
Yes. I just switched to Caddy and had been using nginx before. This is what my config looked like:
server {
server_name www.kamal.io kamal.io;
listen 80;
listen 443 ssl http2;
if ($http_user_agent ~* ^curl) {
set $redirect N;
rewrite ^/$ /about.json;
break;
}
# I had my website behind Cloudflare, so I needed to check the X-Forwarded-Proto header.
# If you're using plain Nginx, use if ($https = "") instead.
if ($http_x_forwarded_proto = "http") {
set $redirect "${redirect}Y";
}
if ($redirect = Y) {
return 301 https://kamal.io$uri;
}
... rest of config ...
}
Without HTTPS, you can use a simpler config:
location / {
if ($http_user_agent ~* ^curl) {
rewrite ^/$ /about.json;
break;
}
}
Another option would be splitting your server blocks into separate HTTP and HTTPS server blocks (which I preferred when I used nginx) and then adapting the snippet above to them.