300

No, not the movie. I'm a nerd who's spent half their day so far configuring nginx, it should be obvious at this point.


Since I have my code 1) on my self-hosted Gitea (soon Forgejo) instance, and 2) mirrored on GitHub, I didn't just want to set up a simple 308 Permanent Redirect from /.git to my code in one place. I wanted users to be able to see both options, both Gitea and GitHub, and apparently there's actually a thing for that already, I can just use that!

ok but how?

300 seems great, but there's no standardized way to use it. The number, and the Location header as the preferred choice is all that's actually standardized. Straight from RFC 9110:

If the server has a preferred choice, the server SHOULD generate a Location header field containing a preferred choice's URI reference. The user agent MAY use the Location field value for automatic redirection.

For request methods other than HEAD, the server SHOULD generate content in the 300 response containing a list of representation metadata and URI reference(s) from which the user or user agent can choose the one most preferred.

Does this mean to return some HTML? A JSON dict? YAML? An image of a pickle with that info overlaid on top of it!? Nobody knows! And from the Mozilla docs:

As there is no standardized way of choosing one of the responses, this response code is very rarely used.

Very helpful.

ok but actually how?

After a bit of Googling, I found this Stack Overflow thread, which just says this[note]:

The "multiple choices" are done by sending the links in hypertext (HTML) content and let the user pick.

Well, that's boring. Guess I'll do that then.

    location  /.git {
        add_header Location "https://git.askiiart.net/askiiart/askiiart-net";
        return 300 '<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"></head><body><a href="https://git.askiiart.net/askiiart/askiiart-net">Self-hosted</a><br><a href="https://github.com/askiiart/askiiart-net">GitHub</a></body></html>';
    }

nginx is hard

That config looks fine, right? It just sets the Location header and returns this basic menu to pick from. But it won't open in any browsers, Firefox displays nothing, and has NS_ERROR_WONT_HANDLE_CONTENT in the developer tools, and Chrome shows a ERR_INVALID_RESPONSE error. Running curl -I https://askiiart.net, and...

HTTP/1.1 300 
Server: nginx/1.25.3
Date: Wed, 06 Dec 2023 16:36:01 GMT
Content-Type: application/octet-stream
Content-Length: 223
Connection: keep-alive

There's no Location header there! And the browsers are probably refusing to show it because of the wrong Content-Type—and no, adding a header with add_header doesn't work for that one, either.

Turns out in order to be able to use add_header, you first need to set default_type (which sets a default Content-Type). I could've set that in the location /.git block, but I just decided to make it global.

So here's the fixed config:

    # actually much higher in file
    default_type "text/html";

    location  /.git {
        add_header Location "https://git.askiiart.net/askiiart/askiiart-net";
        return 300 '<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"></head><body><a href="https://git.askiiart.net/askiiart/askiiart-net">Self-hosted</a><br><a href="https://github.com/askiiart/askiiart-net">GitHub</a></body></html>';
    }

Yep, that works! I had set up my IP address pages the same way, so let's fix that:

    location /ip {
        default_type text/plain;
        return 200 "$remote_addr";
    }

    location /ip/json {
        default_type application/json;
        return 200 '{"ip":"$remote_addr"}';
    }

These still don't work, they just return my reverse proxy container's gateway's address, but that's for another time.

Turns out might be a standard way to do it! From RFC 9110, again:

It is possible to communicate the list using a set of Link header fields RFC5988, each with a relationship of "alternate", though deployment is a chicken-and-egg problem.

I'll probably figure out the Link header some other time and will add this later, at which point I'll post an addendum. Until then, bye I guess!

Footnotes and stuff

browser-specific info

Elsewhere in the Quora thread it says, in short, that Firefox and Chrome just displays the HTML, and Safari will follow the Location header. [Back]