A minimal, zero-dependency file manager powered entirely by Nginx.
Most self-hosted file managers require a Python, Node, or PHP backend. nginx-explorer needs only Nginx. The entire application is one .js file, one .css file, and an Nginx config — no build step, no npm install, no app server.
- No backend code — Nginx handles file serving, auth, and routing
- No JavaScript dependencies — vanilla JS, runs in any browser
- LAN auto-login — local network clients get access without credentials
- Multi-user with per-path ACL — each user can be scoped to different directory trees
- Chunked uploads — reliably transfer large files without timeouts
- Smart media mode — auto-detects photo galleries and TV episode directories and sorts accordingly
- Brute-force protection — login rate-limited to 1 request/minute per IP via
limit_req - Hardened container — runs with
--cap-drop=ALL, read-only mounts, and a tmpfs temp dir
Serve the current directory on port 8080:
git clone https://github.com/izissise/nginx-explorer.git
nginx-explorer/ngxp.sh download_icons
nginx-explorer/ngxp.sh servethisRequires: Docker or Podman.
ngxp.sh servethis is a convenience wrapper. For a permanent or customised deployment, use the container directly.
Add --userns=keep-id when using Podman to map the container UID to your host user.
NGXP=/path/to/nginx-explorer # repo checkout
FILES=/path/to/your/files
UPLOADS=/path/to/upload/destination
docker run --rm -it \
--user="$(id -u):$(id -g)" \
--cap-drop=ALL \
--tmpfs=/tmp:rw,noexec,nosuid,size=70m \
-p 8080:8080 \
-v "$FILES:/home/user/downloads:ro" \
-v "$UPLOADS:/home/user/uploads:rw" \
-v "$NGXP/docker_nginx.conf:/etc/nginx/nginx.conf:ro" \
-v "$NGXP/nginx-explorer.conf:/etc/nginx/conf.d/default.conf:ro" \
-v "$NGXP/icons:/var/www/ngxp/icons:ro" \
-v "$NGXP/main.js:/var/www/ngxp/main.js:ro" \
-v "$NGXP/main.css:/var/www/ngxp/main.css:ro" \
-v "$NGXP/basic.htpasswd:/opt/ngxp/basic.htpasswd:ro" \
-v "$NGXP/accessuri.map:/opt/ngxp/accessuri.map:ro" \
nginx| Container path | Purpose | Writable? |
|---|---|---|
/home/user/downloads |
Files served for listing and download | No |
/home/user/uploads |
Destination for uploaded chunks | Yes |
/var/www/ngxp/ |
JS, CSS, icons (app assets) | No |
/opt/ngxp/basic.htpasswd |
User credentials | No |
/opt/ngxp/accessuri.map |
User → paths mapping | No |
/etc/nginx/nginx.conf |
Base Nginx config (docker_nginx.conf) |
No |
/etc/nginx/conf.d/default.conf |
nginx-explorer Nginx config | No |
The --tmpfs=/tmp mount is required — Nginx uses /tmp for upload buffering and its own internals. Without it the container will fail to start under --cap-drop=ALL.
Nginx's built-in autoindex generates the directory listing HTML. nginx-explorer injects itself into that HTML via sub_filter, replacing the <html> tag with a <script> tag that bootstraps the JS UI. No server-side templating, no dynamic code path — the same standard Nginx that serves static files powers the entire application.
Authentication is handled entirely through Nginx maps:
- A
POST /___ngxp/loginendpoint validates HTTP Basic Auth credentials (bcrypt-5, rate-limited to 1 req/min per IP) - On success, Nginx sets a cookie containing
user|secret|allowed_paths - Subsequent requests are validated by matching the cookie's secret against
accessuri.mapusing regex maps — no session store, no database
ngxp.sh is the single entry point for all local operations:
| Command | Description |
|---|---|
download_icons |
Downloads the KDE Breeze icon pack (32×32 SVGs) into icons/. Run once before first use. Skips if already present. |
servethis |
Adds a lan_anon user with access to /, then starts an Nginx container serving $PWD on port 8080. LAN clients browse without a login prompt. |
dev |
Starts a dev server with several pre-created test users (root, sub, upload, xx) and mounts ~/Downloads. Useful for iterating on the UI. |
user_add <htpasswd> <accessmap> <user> <pass> <path...> |
Atomically creates or replaces a user in both basic.htpasswd (bcrypt-5 hash) and accessuri.map (random 128-char hex secret). Accepts multiple paths. |
upload_fixup <upload_dir> |
Scans upload_dir for chunk metadata files (identified by the #ngxpupload_meta magic prefix), validates chunk sizes, concatenates them into the original file, and removes the temporary chunks. |
test |
Downloads BATS and its assertion libraries on first run, then runs all test/*.bats integration test files in parallel Nginx containers. |
./ngxp.sh user_add basic.htpasswd accessuri.map <username> <password> <path> [<path2> ...]Example — a user with access to two separate directory trees:
./ngxp.sh user_add basic.htpasswd accessuri.map alice secret123 /photos /documentsEach line maps a username to its secret token and allowed paths:
alice alice|<secret>|/photos|/documents;
The secret is generated automatically by user_add. Users cannot forge cookies without knowing it. You can grant a user access to multiple disjoint trees by listing multiple paths.
| Path | Default |
|---|---|
| Files (listing/download) | /home/user/downloads |
| Upload destination | /home/user/uploads/ |
| App files (JS, CSS, icons) | /var/www/ngxp/ |
| User access map | /opt/ngxp/accessuri.map |
| Passwords file | /opt/ngxp/basic.htpasswd |
Clients on RFC-1918 ranges (192.168.x.x, 10.x.x.x) and loopback are classified as lan_anon by Nginx's geo block; all other clients are classified as wan_anon. These are treated as regular usernames looked up in accessuri.map.
If lan_anon or wan_anon are not present in accessuri.map, anonymous access is blocked by default — the path check fails and Nginx returns 401. The servethis command automatically adds lan_anon with access to /. For a fully private setup (login required from everywhere), simply don't add either entry.
Files are uploaded in 256 MB chunks and stored as temporary files. To reassemble them into the original file, run:
./ngxp.sh upload_fixup /path/to/uploads/A metadata file (prefixed #ngxpupload_meta) is created alongside the chunks, recording the original filename, chunk count, sizes, and file numbers needed for reconstruction. The upload endpoint can be removed entirely from nginx-explorer.conf to disable uploads.
./ngxp.sh dev # Start dev server (mounts ~/Downloads, port 8080)
./ngxp.sh test # Run integration tests (BATS + Docker/Podman)Run a single test file:
./test/bats/bin/bats test/auth.batsRun a single test by name:
./test/bats/bin/bats test/auth.bats --filter "login success"Frontend unit tests (QUnit) are available at http://localhost:8080/___ngxp/qunit.html when the dev server is running.
Copy the relevant location blocks from nginx-explorer.conf into your server config. The ___ngxp prefix is reserved for all app routes to avoid colliding with directory names. If you have a directory literally named ___ngxp, it will not be listable.
Three in-depth posts covering how nginx-explorer works internally:
- Nginx Explorer — File Listing — Build a file-sharing interface using Nginx's
autoindexandsub_filterfor CSS/JS injection. - Nginx Explorer — Cookie Authentication — Implement per-user, per-path cookie authentication using only Nginx
mapdirectives. - Nginx Explorer — Upload — Enable large file uploads via chunked JS uploads and server-side Bash reassembly.
