Deployment guide

Audiobookshelf — Self-Hosted Audiobook and Podcast Server on VPS

Deploy on a VPS Cloud →

Tutorial

Self-hosting

Audiobookshelf — Self-Hosted Audiobook and Podcast Server on VPS

Audiobookshelf is a self-hosted, open-source (GPL-3.0) audiobook and podcast server. Unlike cloud services, your audio files stay on your server — you own them entirely, with no subscription, no quota limits and no listening-habit tracking. The official iOS and Android apps — fully open-source — connect to your instance and sync progress, bookmarks and playlists across all your devices.

12 min read11 steps
Contents· Why self-host your audio library?1/16
  1. 01Why self-host your audio library?
  2. 02What Audiobookshelf brings to a ServOrbit VPS
  3. 03Supported audio formats
  4. 04Requirements and sizing
  5. 05Deploy Audiobookshelf with Docker Compose
  6. 06Setup without a domain name
  7. 07Volume permissions and running as non-root
  8. 08Named volume or host folder for /config and /metadata
  9. 09Consistent backups: what the built-in backup covers
  10. 10Cold backup and manual restore
  11. 11Updates and versions: pin rather than follow latest
  12. 12Troubleshooting: restart loop at startup (EPERM … copyfile)
  13. 13Troubleshooting: the nightly backup crashes the container (SQLITE_BUSY)
  14. 14Other common errors
  15. 15Minimal hardening
  16. 16Going further

Why self-host your audio library?

Audiobook platforms impose their catalogues, their DRM and their monthly fees. With Audiobookshelf you import your own M4B, MP3 or FLAC files — bought, borrowed or produced yourself — and reach them from any device through the open-source mobile apps. The library grows at your pace, not theirs.

What Audiobookshelf brings to a ServOrbit VPS

  • Streaming and sync — resume an audiobook where you left off, on the web or on mobile, without a third-party cloud account.
  • Open-source iOS and Android apps — the official clients are GPL-3.0, auditable and maintained by the community.
  • Built-in scheduled backups — the server can produce an archive of its own database and metadata, with adjustable retention.
  • Built-in podcasts — subscribe to RSS feeds, episodes are downloaded automatically and play in the same interface.
  • Multi-user — each family member gets their own account, history and playlists.
  • Automatic metadata — covers, chapters and descriptions can be fetched from providers such as Audnexus, with no manual entry.

Supported audio formats

Audiobookshelf plays the common formats, including MP3, M4A, M4B (with chapters), AAC, OGG, FLAC, WAV and OPUS. M4B chapters appear in the player with their title and duration. DRM-protected Audible files do not play as they are: only import files you have the right to use, in an open format.

Requirements and sizing

The server itself is modest: an Ubuntu 24.04 VPS with Docker is enough for a family. What weighs is disk, because the library (above all podcast episodes, downloaded over time) keeps growing, and CPU only when a client asks for transcoding through ffmpeg instead of direct playback. Size your data volume on your collection, and leave room for metadata, covers and backups, which live by default in the same /metadata volume.

The server's SQLite database must stay on the VPS's local disk. The Audiobookshelf documentation is explicit: the /config folder must not be mounted on a network share, or you risk slowdowns and then corruption. The audio files themselves can live elsewhere.

Deploy Audiobookshelf with Docker Compose

  1. Prepare the VPS and Docker

    Connect over SSH, update the system, then install Docker and the Compose plugin. Check with docker compose version. On a ServOrbit VPS the Audiobookshelf template does this for you: this guide helps you understand what is running and take it over.

  2. Create the folders and set the owner

    Create one folder per volume: mkdir -p /opt/audiobookshelf/{config,metadata,audiobooks,podcasts}. Pick the user the container will run as (for example 1000:1000), then give it the folders: chown -R 1000:1000 /opt/audiobookshelf. The four folders must be distinct mount points, not nested in one another.

  3. Write the docker-compose.yml

    Declare an audiobookshelf service with the image ghcr.io/advplyr/audiobookshelf (pin the tag of the version you checked rather than latest), restart: unless-stopped, the directive user: "1000:1000", the port 127.0.0.1:13378:80 and the four mounts /opt/audiobookshelf/config:/config, /opt/audiobookshelf/metadata:/metadata, /opt/audiobookshelf/audiobooks:/audiobooks and /opt/audiobookshelf/podcasts:/podcasts. Add TZ=Africa/Casablanca (or your time zone) as an environment variable. Audiobookshelf does not use the PUID and PGID variables: the user: directive sets the user.

  4. Start and check

    Run docker compose up -d, then docker compose logs -f audiobookshelf. On first start the server applies its database migrations: wait for the line announcing it is listening on the container's port 80. If docker compose ps shows the service restarting constantly, go straight to the troubleshooting section below.

  5. Create the admin account and the library

    Open the interface (through an SSH tunnel or your domain), create the root account, then add a library pointing to /audiobooks and another to /podcasts. Start a scan: books appear with their covers and chapters.

  6. Connect the mobile app

    In the iOS or Android app, enter your instance's HTTPS address, your username and password. Progress then syncs between the phone and the web. For permanent use, put an HTTPS reverse proxy in front of the container by following the Audiobookshelf documentation's "Reverse Proxy" page (notably the WebSocket passthrough).

Setup without a domain name

Audiobookshelf does not need a domain name to run. With the ServOrbit template, the container listens only on the VPS loopback, port 13780. Open an SSH tunnel from your machine: ssh -L 13378:127.0.0.1:13780 root@<vps-ip>, then browse to http://localhost:13378. For permanent use from the mobile app, attach a domain from your ServOrbit client area — nginx proxies the container over HTTPS — or use the free subdomain {app}.{dns_slug}.servorbit-dns.com included with every VPS.

Volume permissions and running as non-root

Four volumes, four roles. /config holds the SQLite database and the migration scripts. /metadata holds metadata, covers, author images, logs, the cache and, by default, backups. /audiobooks and /podcasts give access to the media files. The server must be able to write to /config and /metadata; on /audiobooks, writing is only needed if you want it to write metadata next to the files, and on /podcasts it is essential since episodes are downloaded there.

By default, the official example sets no user. To harden, add the user: directive: the container then no longer has root rights on the mounts, and that is precisely where permission errors appear. The rule is simple: the owner of the host folders must be the UID:GID of the user: directive. Test before loading the library: docker compose exec audiobookshelf touch /config/test /metadata/test, then delete both files.

On a filesystem with ACLs (ZFS with NFSv4 ACLs, for example), a chown is not always enough: the incident described below illustrates it, with a container running as user 568, all capabilities dropped, on a ZFS dataset.

Named volume or host folder for /config and /metadata

Scroll the table

CriterionDocker named volumeHost folder
Initial ownerManaged by Docker, often rootYou set it yourself with chown
Compatibility with `user:`Check before the first importDirect, if the UID:GID matches
Cold backupUse `docker volume inspect` to find the pathDirect `rsync` of the folder
Path readabilityLess obviousFixed, known path
Portability to another VPSVolume export to planCopy the folder
Recommended useQuick trial, ready-made templateProduction with a non-root user

Consistent backups: what the built-in backup covers

Audiobookshelf can create its own backups from the interface (schedule, number of backups kept, maximum size). Each archive contains the database and the cover images from the metadata folder; the media files stored with library items are not in it. By default, archives are written to /metadata/backups, that is on the same disk as the data: a VPS failure takes both. Copy them off the server regularly.

A backup only protects if it is consistent. The database is a live SQLite file: copying /config with cp or rsync while the server writes (listening progress, scans) can produce an inconsistent file. Two safe methods: use the server's built-in backup, then copy the archive it produced; or stop the container for the duration of the copy. Audio files are backed up separately, with rsync or a deduplicating backup tool.

A restore is done on the same kind of system: the documentation warns that a Linux backup is restored on Linux, and that switching from one installation method to another can lose progress and metadata.

Cold backup and manual restore

  1. Stop the container

    From the compose folder, run docker compose stop audiobookshelf. No writes are in progress on the database any more.

  2. Copy the configuration and metadata

    Copy /opt/audiobookshelf/config and /opt/audiobookshelf/metadata to a location off the VPS with rsync -a, keeping permissions. Add the audio folders according to your media backup strategy.

  3. Restart and check

    Run docker compose start audiobookshelf and check in the logs that the server is listening again. The interruption lasts as long as the database copy.

  4. Restore from a server archive

    Stop the container, unzip the most recent archive from metadata/backups, copy audiobookshelf.sqlite into the configuration folder, replacing the existing file, then copy the metadata-authors and metadata-items folders to authors and items. If the archive is unusable, try the previous one. The Backups page of the interface also offers a restore button.

  5. Test a dry-run restore

    Once a quarter, restore a backup on a test VPS and check that users, progress and libraries are there. A backup never restored remains a hypothesis.

Updates and versions: pin rather than follow latest

The ServOrbit template pulls the latest image. It is convenient, but it exposes you to regressions from one version to the next. The documented update is simple: docker compose pull, docker compose down, docker compose up --detach. Before each update, take a backup (built-in or cold) and read the release notes on the GitHub repository's releases page.

A recent case justifies this. Version 2.37.0 moved the server to Node.js 24, and 2.37.1 was hit by a restart loop on some installations (see below). The fix is published in 2.37.2, which is, at the time of writing, the latest stable version listed by the project. A pinned tag and a prior backup let you roll back without improvising.

Troubleshooting: restart loop at startup (EPERM … copyfile)

Symptom: the container restarts in a loop after an upgrade to 2.37.1, and the logs show "Failed to copy migrations to the config directory", then "EPERM: operation not permitted, copyfile" from the /app/dist-server/server/migrations/ folder to /config/migrations/, followed by "Database migration failed".

Actual state of the incident (issue no. 5637 of the advplyr/audiobookshelf repository, opened in October 2026): it is closed. The reporter used TrueNAS SCALE, a container running as user 568, all capabilities dropped and no-new-privileges, with /config on a ZFS dataset as a bind mount. Several TrueNAS users reported the same thing. No root cause is formally established in the discussion; a test showed copyFileSync failing with EPERM on that mount while writing a file directly worked with the same user, and a regression in the copy path linked to Node.js 24 is suspected. The fix (PR no. 5668) adds a fallback when the copy fails; it ships in 2.37.2.

Remedy: upgrade to 2.37.2 or later. If you cannot update right away, the workaround mentioned in the issue is to go back to 2.36.1 with a backup taken before the update. If the same message persists after the update, check the owner of the configuration folder and the dataset ACLs: it is no longer that bug.

Troubleshooting: the nightly backup crashes the container (SQLITE_BUSY)

Symptom: the server stops at the scheduled backup time, with "FATAL: [Server] Uncaught exception" and SQLITE_BUSY: database is locked. With restart: unless-stopped, Docker restarts the container: you only see a brief outage, revealed by a non-zero restart counter (docker inspect --format '{{.RestartCount}}' <container>; replace the name with your container's).

Actual state (issue no. 4572, opened in August 2025): it is still open. A contributor explains the cause: the SQLite backup emits the "database busy" error as an event with no listener, which Node.js treats as an uncaught exception, and no retry is made. The crash happens when another write, for example a client saving listening progress, holds the lock for more than a second during the copy. PR no. 5660 (batched backup that retries on lock, and session errors logged instead of crashing) is open and not merged to date: no published version contains it. An earlier competing PR was closed in favour of this one.

In the meantime, three workarounds reported in the issue: move the backup away from listening hours (dawn rather than evening); keep restart: unless-stopped so the container comes back on its own; or replace the built-in backup with a scheduled cold backup on the host, which stops the container for a few moments. Reminder from the documentation: the database must be on a local disk, never on a network share.

Other common errors

  • Permission denied in the logs — the owner of the host folders does not match the UID:GID of the user: directive. Fix with chown -R, then restart.
  • Empty library after the scan — the library path is not the container's path. Add /audiobooks or /podcasts, not a host path.
  • "Cannot find module '/index.js'" — the documentation points to a stale image cache in some container managers: recreate the container or stack after a docker compose pull.
  • The mobile app cannot connect — check the HTTPS address, the certificate and the WebSocket passthrough in the reverse proxy; an SSH tunnel only serves the browser.
  • Container "unhealthy" — the health check queries the container's port 80: look at docker compose logs to see whether a migration fails at startup.

Minimal hardening

Pin the image tag, run the container with user:, publish the port only on 127.0.0.1 behind an HTTPS reverse proxy, copy the archives from /metadata/backups off the VPS every week and test a restore. This guide is provided as is: ServOrbit does not support the application itself.

Going further

Once the server is stable, add a second layer of protection: an encrypted backup of the configuration folders to remote storage, and an alert if the container restarts more than once a day. Keep following the issues cited here: the backup crash will need checking again as soon as PR no. 5660 is merged and released.

Deploy Audiobookshelf on your VPS

ServOrbit provisions an Ubuntu 24.04 VPS and launches Audiobookshelf in a Docker container within minutes — no manual configuration required. Your audiobooks, your rules, your infrastructure.