===============
= ptrcnull.me =
===============

Serving extensionless files with Nginx

Correct MIME types with only minimal hacks required!

So, a little backstory first: for the past few years I've been hosting random files on https://ptrc.gay using lighttpd, with mod_mimemagic to automatically assign the MIME type based on the file contents. At some point the module broke though, and so that was replaced with a slightly less kosher bodge:

http-response set-header content-type image/png if { res.body,hex -m beg 89504E47 }
http-response set-header content-type image/gif if { res.body,hex -m beg 47494638 }
http-response set-header content-type image/jpeg if { res.body,hex -m beg FFD8FF }

During the migration to a new server I decided to set up Nginx, and I was faced with the choice: either use the third-party nginx-mime-magic-module dynamic module, or figure out a more proper, long-term solution. With how much the former option seemed like effort, I picked the latter and implemented libmagic support in my uploader, writing the files down onto the disk with correct extensions in the first place. That solution didn't apply retroactively though, and I was now left with thousands of files that already had permalinks posted in various chat logs, all without extensions, Nginx happily serving them as application/octet-stream.


With all that context, here's the magic to make Nginx pick up the correct type and keep all URLs working as before:

1. Assign extensions to all files

You can do it however you like, I chose to write a shell oneliner with file(1) for it and manually assign an extension for each recognised type:

$ find . -type f -maxdepth 1 | rg '\./[a-zA-Z]{8}$' | xargs file |\
    rg 'PNG image data' | cut -d: -f1 |\
    while read -r line; do mv $line $line.png; done

Repeat for every single file type you'd like; I chose only those that can be previewed in a browser: images, text files, etc., omitting archives and other non-previewable files.

2. Simply try all the files

Slighly abuse the try_files directive to brute-force the extension when the file's not found:

location / {
    include location_params;
    root /var/www/ptrc.gay;

    try_files $uri
        $uri.diff
        $uri.gif
        $uri.jpg
        $uri.json
        $uri.jxl
        $uri.mbox
        $uri.mkv
        $uri.mp3
        $uri.mp4
        $uri.pdf
        $uri.png
        $uri.tiff
        $uri.txt
        =404;
}

The trick here is that even though the URL itself doesn't contain an extension, having one in the file path is enough for Nginx's MIME type detection to kick in, making sure the correct type is served to the user. While it does look kinda horrendous – and makes way too many I/O operations than necessary – it also avoids re-guessing the type with every single request (more or less what happens with nginx-mime-magic-module). Assigning extensions manually also gives you the flexibility of fixing up the MIME type whenever necessary, e.g. returning text/plain instead of application/mbox – because even though it's 2025, Firefox still can't display the latter as plain text...