httpserver
A directory of files over plain HTTP, and nothing else. No accounts, no
database, no JavaScript — nginx with autoindex on pointed at a scratch
directory. Read-only: it is for handing files out, not taking them in.
The point is reach, not features. This is the share that works from a
Windows 98 machine, a games console browser, a printer's built-in fetcher, or
wget on a router. copyparty and filebrowser
are nicer for humans on modern machines; neither will talk to a client from 1999.
Quick start
cp .env.example .env
$EDITOR .env # TEMPDISK -- the parent of the Temp/ you want served
mkdir -p "$TEMPDISK/Temp"
docker compose up -d
Open http://<host>:8083.
Ports
| Port | Proto | Purpose |
|---|---|---|
| 8083 | http | Directory listing and downloads |
No HTTPS, deliberately. See below.
Why it looks like this
Plain HTTP is the feature, not an oversight. Windows 98 cannot do TLS 1.2,
SNI, or any cipher suite a modern server will negotiate. The moment this goes
behind an HTTPS-terminating proxy it stops working for exactly the clients it
exists for. Verified: an HTTP/1.0 request with no Host: header — which
is what ancient clients send — returns 200 OK, because the single server block
is the default server.
The generated listing is deliberately primitive. nginx's autoindex emits
<html><head><title> then <h1>, <hr>, <pre> and <a href>. No CSS, no
JavaScript, not even a table. That renders in IE5, Netscape 4, Lynx, and
everything since. Responses carry Content-Length, Last-Modified and
Accept-Ranges: bytes, so resume works — which matters more on a flaky old
machine than a modern one.
include mime.types is there despite the minimalism, because it makes weak
clients behave better and costs nothing. Mounting a config over
/etc/nginx/nginx.conf replaces the whole file, including nginx's default
types block, leaving only a tiny built-in table. Measured, before and after:
| File | Without the include | With it |
|---|---|---|
photo.jpg |
image/jpeg |
image/jpeg |
page.html |
text/html |
text/html |
photo.png |
text/plain |
image/png |
archive.zip |
text/plain |
application/zip |
setup.exe |
text/plain |
application/octet-stream |
manual.pdf |
text/plain |
application/pdf |
song.mp3 |
text/plain |
audio/mpeg |
Only html, gif and jpeg survive without it. A modern browser usually shrugs and
downloads anyway; IE5 on Windows 98 tries to display a text/plain
response, so a 700 MB zip becomes a screen of garbage instead of a Save
dialog. The directive is parsed once at startup — no per-request cost, and the
file already ships in the image.
No upload path. nginx can accept WebDAV PUT, and an earlier version of
this config did — but autoindex has no upload form, so a browser can never use
it. Writes would only work from a DAV client: curl -T, or Windows Explorer's
Map network drive. A real upload button needs a CGI or an app behind nginx,
which is the complexity this stack exists to avoid. Use
copyparty or filebrowser when you need
people to send files to you.
The config lives in init/ because this repo's .gitignore ignores
everything inside a service directory except a short list, and init/** is on
it. A config in any other subdirectory is silently not committed, and then the
bind mount has nothing to mount — Docker creates a directory at that path and
nginx won't start. Same failure mode as
overleaf's init script.
Gotchas
PUIDandPGIDdo nothing. This is the official nginx image, not LinuxServer; there is no PUID/PGID handling anywhere in its entrypoint. They are accepted and read by nobody — the same trap documented in audiobookshelf.TZaffects log timestamps at most. It costs nothing here because the share is read-only: the workers run as uid 101 and only ever need read access.${TEMPDISK}has no default. Unset, Compose interpolates an empty string and nginx serves/Tempfrom the host root rather than failing. The repo's${VAR:?message}pattern exists for exactly this.- Keep filenames ASCII for old clients.
autoindexemits filenames as raw bytes; Windows 98 is CP1252, so accented names mangle. :latestis unpinned. If server weight matters,nginx:alpine-slimis 8 MB compressed against 63 MB fornginx:latest, with identical behaviour for static serving.- Anyone who reaches the port sees everything.
autoindexpublishes a browsable listing of the whole share with no authentication. Nothing is writable, so the risk is disclosure rather than tampering — but it is the whole directory, and that is the intended trade for a temporary share on a trusted network.
Exposing it
Think twice. If you put this behind a proxy for remote access, you lose the old clients that justify it existing.
- Reverse proxy with Caddy — fine for modern clients; it will terminate TLS and break Windows 98.
- Authentik forward auth — the way to make it safe remotely, but it needs a browser that can complete the login, which rules out the same ancient clients.
Running two hostnames — one bare HTTP on the LAN, one proxied for outside — is the honest way to have both.
Links
- nginx
autoindex: https://nginx.org/en/docs/http/ngx_http_autoindex_module.html - Image: https://hub.docker.com/_/nginx