OpenFactory Audiobookshelf lab with nginx reverse proxy and Prometheus observability

Build an Audiobookshelf Library on OpenFactory

A three-VM audiobook + podcast library: Audiobookshelf + nginx + observability, from one prompt

April 30, 2026

← Back to Blog

Audiobookshelf is the self-hosted audiobook + podcast server that does what Audible and Spotify Podcasts do, without the algorithm and without the lock-in. It rounds out the top 10 most-deployed apps in the 2026 r/selfhosted survey. You point it at a folder of audiobooks and podcasts, and it gives you a clean library with cover art, chapter navigation, per-user progress that syncs across devices, and native iOS and Android apps with offline downloads — all running on hardware you own.

Two things make it the default pick for spoken-word audio in 2026. First, it understands audiobook formats natively: it plays MP3, M4B, M4A, FLAC, and OGG, and it manages ebooks (EPUB, PDF, MOBI, AZW3, CBR, CBZ) in the same library (Audiobookshelf docs). Second, since the 2.0 release it ships a built-in automated podcast handler that watches RSS feeds and downloads new episodes on a schedule, plus a metadata scraper that pulls covers and details from Audible, Google Books, and Open Library. The project keeps moving — a recent update added a chapter finder that makes jumping around long books far easier (How-To Geek).

This post walks through the Audiobookshelf stack on OpenFactory: three buildable VMs — Audiobookshelf, an nginx reverse proxy with WebSocket-ready upgrade headers for streaming, and a Prometheus observability node — from one prompt, shipped as bootable ISOs.

What you'll build

  • audiobookshelf (10.79.0.10:13378) — the library server with /var/lib/audiobookshelf/{config,metadata,audiobooks,podcasts} pre-wired.
  • nginx-proxy (10.79.0.20:80) — reverse proxy with Connection: upgrade headers so client streaming and live progress sync work through it.
  • observability (10.79.0.30:9090/3000) — Prometheus + Grafana scrape targets ready for the live stack.
  • Pre-created library directories. config, metadata, audiobooks, and podcasts under /var/lib/audiobookshelf exist at first boot with 0750 ops:opspermissions — mount your real content over them and the env file already points the right way.
  • Health, status, and metrics endpoints. The compatibility service answers /healthcheck, /status, /ping, and /metrics, so the proxy and the Prometheus node have something real to probe before the Node binary lands.

Why build it on OpenFactory

  • The ISO is the spec. Library paths, env file, and proxy vhost all bake into bootable images — no hand-editing nginx.conf on a fresh box, no guessing which directory the env file expects.
  • WebSocket-ready proxy out of the box. Audiobookshelf relies on WebSockets for live progress sync between your phone and the web player; a proxy that drops the Upgrade / Connection headers silently breaks that. The recipe sets them upfront, which is the single most common reverse-proxy mistake people hit with this app.
  • Three VMs, easy to grow. Same prompt scales to add a transcoder VM or a second proxy without rebuilding the library node. Because Audiobookshelf runs comfortably on modest hardware — even an ARM Raspberry Pi — the 2 GB library VM here has real headroom.
  • Scenario assertions ride along. The build fails closed if the healthcheck or status endpoints regress, or if the proxy can't reach the library on :13378. The wiring is verified at build time, not discovered when a download won't play.

Topology

Three Debian Trixie VMs on 10.79.0.0/24. The nginx proxy is the front door: clients hit it on :80 and it forwards to the library on :13378, carrying the WebSocket upgrade so live progress sync survives the hop. The observability node scrapes both the library and the proxy, and exposes Prometheus on :9090 and a Grafana endpoint on :3000.

Audiobookshelf topology on 10.79.0.0/24 (nginx proxy, library, observability)subnet 10.79.0.0/24 (runner network):80:13378 wsscrape /metricsapp / browseraudiobookshelf10.79.0.10:13378nginx-proxy10.79.0.20:80observability10.79.0.30:9090 / :3000
Solid arrows carry playback traffic (with the WebSocket upgrade); dashed arrows are the observability node scraping /metrics from both services.

The prompt

Paste this verbatim into the chat builder at console.openfactory.tech. Nothing above or below it — the builder expects the prompt body to start at the “Build a compact multi-node lab…” line.

Build a compact multi-node lab named `audiobookshelf-library`.

Output discipline: keep the plan small. Use one startup script per node, about 25 shell lines or less. Do not install the real Audiobookshelf Node.js binary, FFmpeg, or external `apt` repos at build time. Write deployment-time config examples and tiny Python stdlib or shell compatibility stubs only. The goal is a buildable preparation lab, not a production deployment.

## Topology

Create 3 buildable `debian-trixie` nodes, all `x86_64`, SSH enabled, DHCP/default route intact with lab aliases, firewall disabled, DNS `1.1.1.1` and `8.8.8.8`, user `ops` password `abs-ops` in `sudo`. Every recipe must set top-level `test_config` to `{ "enabled": false, "tests": [] }`.

- `audiobookshelf`: role `audio-library`, 2 GB RAM, 24 GB disk, alias `10.79.0.10/24`, x `230`, y `60`
- `nginx-proxy`: role `reverse-proxy`, 1 GB RAM, 8 GB disk, alias `10.79.0.20/24`, x `110`, y `220`
- `observability`: role `observability`, 2 GB RAM, 16 GB disk, alias `10.79.0.30/24`, x `350`, y `220`

Connections: `nginx-proxy` to `audiobookshelf:13378`; `observability` to all nodes.

## Common Recipe Requirements

All nodes: features `headless`, `ssh`; packages `openssh-server`, `python3`, `curl`, `jq`, `iproute2`, `netcat-openbsd`, `ca-certificates`. Each startup script adds the alias with `IFACE=$(ip route show default | awk '{print $5; exit}')`, `ip link set "$IFACE" up || true`, and `ip addr add <alias> dev "$IFACE" || true`. If `os.startup_scripts[].after` is present, it must be the string `"network-online.target"`, not an array.

## Node Requirements

`audiobookshelf`: features `headless`, `ssh`. Write `/etc/audiobookshelf/abs.env` with `PORT=13378`, `CONFIG_PATH=/var/lib/audiobookshelf/config`, `METADATA_PATH=/var/lib/audiobookshelf/metadata`, `AUDIOBOOKS_PATH=/var/lib/audiobookshelf/audiobooks`, `PODCASTS_PATH=/var/lib/audiobookshelf/podcasts`. Create `/var/lib/audiobookshelf/{config,metadata,audiobooks,podcasts}` mode `0750 ops:ops`. Add a Python stdlib HTTP service on `0.0.0.0:13378` exposing:
- `GET /healthcheck` -> `200 {"status":"ok","node":"audiobookshelf"}`
- `GET /status` -> `200 {"app":"audiobookshelf","serverVersion":"compat-1.0","isInit":true,"language":"en-us"}`
- `GET /ping` -> `200 {"success":true}`
- `GET /metrics` -> `audiobookshelf_compat_up 1`
Register `audiobookshelf-compat.service`.

`nginx-proxy`: features `headless`, `ssh`, `nginx`; packages `nginx`. Write `/etc/nginx/sites-enabled/abs.conf` proxying `:80` to `http://10.79.0.10:13378` with WebSocket upgrade headers (`proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";`) for streaming playback. Restart nginx.

`observability`: features `headless`, `ssh`, `prometheus`, `monitoring`; packages `python3`. Write `/etc/prometheus/prometheus.yml` scraping `10.79.0.10:13378` and `10.79.0.20:80`. Add tiny Python listeners for `:9090/-/healthy` and `:3000/api/health`.

## Scenario

Emit exactly one group scenario named `audiobookshelf-library-validation`. Put `custom_tests[].assertions[]` inside the scenario entry; leave `scenarios[].tests` empty. Every assertion needs `on_vm`. Use only `port_listening`, `command_output`, and `http_responds`; do not emit `vm_boots`, `network_reachable`, or `service_running`.

- `Stack ports listen`: `port_listening` for `audiobookshelf:13378`, `nginx-proxy:80`, `observability:9090`, `observability:3000`.
- `Audiobookshelf healthcheck`: on `audiobookshelf`, `curl -fsS http://localhost:13378/healthcheck | jq -e '.status == "ok"' >/dev/null && echo abs-ok`.
- `Audiobookshelf status`: on `audiobookshelf`, `curl -fsS http://localhost:13378/status | jq -e '.app == "audiobookshelf" and .isInit == true' >/dev/null && echo status-ok`.
- `Proxy reaches abs`: on `nginx-proxy`, `nc -z -w 5 10.79.0.10 13378 && echo upstream-reachable`.
- `Observability`: on `observability`, grep both scrape targets from `/etc/prometheus/prometheus.yml` and `http_responds` for `http://localhost:9090/-/healthy` and `http://localhost:3000/api/health`.

Preserve warnings that real Audiobookshelf Node.js binary install, audio transcoding for streaming, mobile-app push notifications, library scan scheduling, Audible/Libby/Plex integration, off-host backups of `/var/lib/audiobookshelf`, real TLS termination, and `10.79.0.0/24` aliasing are deployment-time concerns.

Running it

  1. Open the chat builder at console.openfactory.tech and paste the prompt into a new conversation.
  2. Review the streamed build plan. You'll see the topology, per-node recipes, and the scenario assertions that will run after boot. Edit the prompt and re-run if anything is off.
  3. Click Build group. OpenFactory fans the plan out to per-node ISO builds. When every ISO reaches built, boot the group on the runner network from the same UI.
  4. Exercise the stack. The scenario assertions run automatically against the live VMs. From the host you can also hit the service ports directly to confirm end-to-end behavior.

Driving OpenFactory from an AI agent instead of the browser? The same flow is exposed through the OpenFactory MCP server — submit the prompt programmatically, get the build-plan preview back, and call create_build / start_vm on the resulting recipes. Single-image builds go straight through the openfactory CLI.

What's still your responsibility

The prompt produces a buildable preparation lab — the right topology, the right ports listening, deployment-time config templates dropped in the right places, and tiny compatibility services that prove the wiring works. A few things still sit outside the recipe and need operator attention before this carries real load:

  • Real Audiobookshelf Node binary. Install from the upstream release; the env file already points at the right paths.
  • Library content. The directories are ready — mount your existing audiobook + podcast collection (or a share from your NAS).
  • Real TLS. Layer the nginx + TLS reverse proxy pattern in front for public access.
  • Mobile-app push. Wire the push-notification keys for the iOS / Android client at deploy.
  • Audio transcoding. Audiobookshelf uses FFmpeg to transcode on the fly when a client can't play a file directly. It's light work compared to video, but it's CPU, not the stub — install FFmpeg with the real binary and give a heavily-used library a little headroom.
  • Sizing. The app itself is frugal — it idles in the low hundreds of megabytes of RAM and runs fine on an ARM Raspberry Pi — so the 2 GB / 24 GB library VM here is generous. The real planning is storage: size the disk (or the NAS share you mount over audiobooks and podcasts) to your collection, and remember podcast auto-download grows it over time.
  • Off-host backups. /var/lib/audiobookshelf/config holds users, listening history, bookmarks, and library metadata. That listening-position data is the thing you'd most hate to lose — back it up off the box.

Where to go next

Audiobookshelf vs. Plex / Plexamp for audiobooks

If you already run Plex, can't you just use it for audiobooks? You can — with caveats. Plex and Plexamp are music-first: they treat books as albums, which means clumsy chapter handling and, the classic complaint, losing your place in a long title. Getting good audiobook metadata into Plex usually means third-party bundle agents and a fair amount of tag-wrangling. Audiobookshelf was built for spoken-word from the start: resume-anywhere progress that syncs across devices, sleep timers, playback-speed memory, bookmarks, and a podcast subscription engine Plexamp simply doesn't have. Plexamp wins if your real goal is a polished music player; for books and podcasts, the purpose-built tool is the easier life.

A few quick questions

Do I need the reverse proxy at all? For LAN-only use, no — you can hit the library on :13378 directly. The proxy earns its keep the moment you want one clean hostname, a single TLS endpoint, or remote access; it's also where the WebSocket upgrade lives, so listening progress keeps syncing through it.

Can my phone download books for offline listening? Yes. The official iOS and Android apps support offline downloads, so a commute or a flight doesn't need a connection back to the server. Progress reconciles the next time you're online.

Will it pull podcasts automatically? That's the headline 2.0 feature: subscribe to an RSS feed and Audiobookshelf checks it on a schedule and downloads new episodes into your podcast library on its own — no separate podcatcher needed.

Where to go next

Audiobooks plus the rest of the media stack: see the Jellyfin media stack post for video, or the Plex media stack post if Plex is already your front-end. For the *arr suite that keeps libraries filled automatically, see the *arr media automation post. The Enterprise & GxP page covers fleet rollouts, and pricing has the plan breakdown.

Ready to ship this in production?

OpenFactory's free flow is for browsing. Persistent VMs, SSH access, snapshots, your own ISO, and fleet deployment live on a paid plan.