Contents

Audiobookshelf: The Self-Hosted Library for Long Drives

Progress sync that actually works, a folder structure that matters, and podcasts as a bonus

Contents

Most self-hosted software has a mobile story that ranges from “there is a progressive web app” to a shrug. That is usually fine. I administer my servers from a desk.

Audiobooks break that assumption completely. The entire use case is a phone, in a car, on a motorway, with a dying signal and no free hands. If the mobile experience is bad, the software is useless regardless of how elegant the backend is. This is the rare category where the app is the product and the server is plumbing.

Audiobookshelf understands this, which is why it is the only thing in my rack whose Android app I would describe as good rather than acceptable. It has also quietly become the best self-hosted podcast client I have used, which is a bonus nobody advertises.

The Problem It Solves

Advertisement

I had four hundred audiobooks as .m4b and .mp3 files on an array. Playing them was a mess of copying files to a phone, losing position between devices, and a permanent low-grade uncertainty about which of six copies of a series I had actually listened to.

The specific pain is progress. A 24-hour book listened to over three weeks across a phone, a car and a laptop needs its position tracked to the second, everywhere, always. Every ad-hoc solution I tried failed at this, and failing at it means restarting a chapter you already heard, which is the exact frustration that makes people give up and pay a subscription.

Audiobookshelf tracks position server-side, per user, per book, and syncs it continuously. It works. That is the whole pitch and it is enough.

Standing It Up

One container. Node.js, SQLite, no external database.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
services:
  audiobookshelf:
    image: ghcr.io/advplyr/audiobookshelf:latest
    restart: unless-stopped
    ports:
      - "13378:80"
    environment:
      AUDIOBOOKSHELF_UID: 1000
      AUDIOBOOKSHELF_GID: 1000
      TZ: Europe/Copenhagen
    volumes:
      - abs_config:/config
      - abs_metadata:/metadata
      - /srv/media/audiobooks:/audiobooks
      - /srv/media/podcasts:/podcasts

volumes:
  abs_config:
  abs_metadata:

Note what is missing: :ro on the library mounts. Audiobookshelf writes back — embedded metadata, downloaded cover art, podcast episodes it fetches on your behalf. If you want it read-only you can have that, at the cost of the features that make it worth running. I let it write, and the array it writes to is SnapRAID-protected bulk storage with a snapshot behind it, which is the compromise I am comfortable with.

The /metadata volume is where cached covers and generated data live. It grows to a few hundred megabytes and it is regenerable, so it is a candidate for exclusion from your backups. The /config volume holds the SQLite database with every user’s listening position, and that one is precious out of all proportion to its size.

First run creates the root user in the browser, then you add libraries and point them at the mounts.

The Folder Structure Is the Configuration

Advertisement

Here is the part that determines whether you love this or fight it for a month.

Audiobookshelf infers structure from directories. There is no tagging pass that rescues a bad layout, and reorganising four hundred books after the fact is a chore. The scanner expects:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
/audiobooks/
├── Ursula K. Le Guin/
│   ├── The Dispossessed/
│   │   ├── The Dispossessed.m4b
│   │   └── cover.jpg
│   └── Earthsea/
│       ├── Book 1 - A Wizard of Earthsea/
│       │   └── A Wizard of Earthsea.m4b
│       └── Book 2 - The Tombs of Atuan/
│           └── The Tombs of Atuan.m4b
└── Iain M. Banks/
    └── Culture/
        └── Book 1 - Consider Phlebas/
            ├── 01 - Chapter One.mp3
            ├── 02 - Chapter Two.mp3
            └── cover.jpg

Author, then optional series, then book. The Book N - prefix is how series order gets parsed and it is worth the tedium of adding — without it, a nine-book series sorts alphabetically and “continue the series” becomes a manual exercise.

A book that is one .m4b with embedded chapters is the ideal case. A book that is 47 loose MP3s works, and Audiobookshelf will treat them as one book with track-derived chapters, though the chapter names will be whatever the filenames say. The built-in m4b merge tool will consolidate them, which takes a while and produces a much better artefact.

Metadata comes from Audible, Google Books, iTunes and Open Library. The Audible provider is by far the best for audiobooks, unsurprisingly, and matches on title plus author with decent accuracy. It gets series numbering right most of the time, and the rest you fix by hand — a quick match on a shelf of four hundred took an evening.

The App

Both platforms have a native app and they are the reason to bother.

What actually matters, in order: downloads for offline (a motorway with no signal is the design centre); playback speed with proper pitch correction, because a lot of audiobook listening happens at 1.4×; a sleep timer that fades out and, crucially, can rewind on resume; chapter navigation from the lock screen; and Android Auto support, which puts the library on the car’s head unit and is the single feature that ended my subscription.

Position sync happens continuously and reconciles when you come back online, so listening offline in the car and picking up on a laptop that evening works exactly as you would want. That reconciliation is the hard engineering problem in this category and it is the thing that is done well.

The gap versus the commercial apps is discovery and polish. There is no “listeners also enjoyed”, no store, no narrator browsing. Fine by me.

Podcasts, the Unadvertised Feature

Add a podcast library and Audiobookshelf becomes a podcast server: subscribe by feed URL, auto-download new episodes on a schedule, keep N most recent, and track progress in the same app.

The thing that makes this better than a normal podcast app is the archive. Podcast apps stream from the publisher’s CDN and episodes disappear when a show gets pulled, remastered, or scrubbed of an old sponsor read. Audiobookshelf downloads the file to your disk. The 2014 episode you remember is still there.

The scheduling is a cron expression in the library settings, and the retention setting is what stops the folder eating your array — a daily news show at 40 MB an episode is 15 GB a year, and left unlimited across a dozen subscriptions it adds up faster than you would think.

Getting It Out of the House

An audiobook server that only works at home is a device for listening to books in the room where the server is, which defeats the purpose.

The two viable approaches are the usual ones and the choice is sharper here than for most services.

A mesh VPN is the safe answer. The app dials the overlay address and neither knows nor cares. The friction is that your phone has to hold a VPN up on a motorway, and the app has to survive the reconnection when you pass through a dead zone — which, to be fair, it does, because the offline download path means it does not need the server at all while playing. Sync catches up when the tunnel comes back.

Exposing it publicly behind a reverse proxy and HTTPS is the other option, and it is defensible for this one because the offline-download model means the exposure window is small and the app handles a real login properly. Put a strong password on the account, keep the container’s image current, and understand that you have now published a Node application to the internet.

What settles it for me is a detail of car use. Android Auto starts playing before the phone has finished thinking about connectivity, and every layer between the app and the audio makes the first thirty seconds of a drive less reliable. Downloaded files sidestep all of it. So my actual configuration is a VPN for sync plus aggressive offline downloads, and the network is irrelevant while I am driving. That is the setup I would recommend, and it is boring, which in a car is the correct aesthetic.

If you are exposing it, one small thing worth doing: the container speaks plain HTTP and expects the proxy to handle TLS, so make sure your proxy sets forwarded headers correctly or the app will build links against the wrong scheme and the mobile clients will fail in a confusing way that looks like an authentication problem.

Troubleshooting

The scan finds nothing. Permissions, almost always. AUDIOBOOKSHELF_UID/GID must match the owner of the files:

1
2
docker compose exec audiobookshelf ls -la /audiobooks | head
stat -c '%u %g %n' /srv/media/audiobooks

Books appear with the author as “Unknown”. The folder is one level too shallow — a book directly under /audiobooks/ with no author folder. Audiobookshelf reads the path, so fix the path.

One book has become 47 books. Loose files at the top of the library rather than inside a book folder. Same cause, same fix.

Progress does not sync. Check that the app is pointed at the same server URL it was when you started. This sounds absurd and it is the most common cause — people set the app up on the LAN address, then start using a domain, and the app treats it as a different server with a separate account.

Everything is slow after adding 400 books. The first scan reads and probes every file, and on spinning disks that is genuinely hours. Watch it rather than restarting the container halfway, which is what I did and which makes it start again.

1
docker compose logs -f audiobookshelf | grep -i -E 'scan|error|probe'

Podcast downloads fail silently. Some feeds require a user agent, and some redirect through a tracker that dislikes non-browser clients. The failure is logged and it is not surfaced in the UI, which is my biggest complaint about the software.

The database is the whole thing. Back up /config. Everything else is reacquirable from files or the internet. That directory holds every listening position and every user, and losing it means four hundred books whose progress is now zero. It is a few megabytes — put it in the same set as your other small-and-precious data, alongside the Paperless-ngx database and everything else where the value is disproportionate to the size.

What It Will Not Do

It is honest to be clear about the boundaries.

It will not get you audiobooks. This is a library, and filling it is your problem — which for most people means DRM’d purchases from a shop, and dealing with that is a legal and technical mess outside the scope of a compose file.

Ebooks are supported and the reader is basic. If ebooks are your main library rather than an afterthought, this is the wrong tool.

There is no OPDS server, so the ebook clients that expect one cannot see it. That is a real gap and it comes up often.

Multi-user works and the permission model is coarse. It is a household tool.

Verdict

If you have a shelf of audiobook files and a commute, install this and stop reading blog posts. It is one container, the app is genuinely good, the progress sync is flawless, and Android Auto in the car is the moment it stops feeling like a hobby project.

The condition is that folder structure. Spend the first hour getting Author/Series/Book right and everything afterwards is pleasant. Skip it and you will spend a month fighting a scanner that is doing exactly what you told it to.

If you have no audiobooks, this solves nothing, and the honest comparison is with a subscription that has a catalogue attached. The self-hosted argument here is the same as it is for music: the shop and the shelf are different things, and you can want both.

Where I have landed is that the podcast half snuck up on me. I installed it for four hundred books and the feature I use daily is the archive of a show that has since deleted half its back catalogue. That is the self-hosting argument in miniature, and it arrived as a side effect.

Advertisement
Advertisement
Smarc
Written by Smarc

Founder and editor of vo.rs. A lifelong tinkerer who self-hosts far more than is sensible, hardens Linux boxes for fun, and prods the latest AI tools to see what they can really do. The how-to guides here are the notes Smarc wishes had existed the first time round.