How to secure your Audiobookshelf server

A practical guide to securing a self-hosted Audiobookshelf server: HTTPS, rate limiting, Cloudflare Access or Tailscale, Docker permissions, API keys, and backups.

Audiobookshelf ships without HTTPS, without two-factor auth, and with a Docker image that runs as root. None of that is insecure by itself, but it does mean the defaults assume you’re going to layer a few things on top. This post walks through what actually matters for a self-hosted Audiobookshelf server, with real config you can copy.

If you only do three things, do these:

  1. Put it behind a reverse proxy with HTTPS so credentials don’t travel over the open internet
  2. Keep the login off the public internet (Cloudflare Access or Tailscale)
  3. Keep the server updated

The rest below is the second tier: smaller things that matter once the above is in place.

1. Put HTTPS in front of Audiobookshelf

Audiobookshelf serves plain HTTP on port 13378 (or 80 in the official Docker image). There is no built-in TLS. Expose that port directly and your password, session cookies, and every API call travel unencrypted.

The fix is a reverse proxy that terminates TLS and forwards to ABS. The reverse proxy setup guide covers Caddy, Nginx, and Traefik. Caddy is the easiest since it handles Let’s Encrypt certificates automatically:

audiobookshelf.yourdomain.com {
    reverse_proxy localhost:13378
}

A few things to get right at the proxy layer:

  • Force HTTPS. Redirect HTTP to HTTPS at the proxy. Caddy does this by default, Nginx needs an explicit return 301 https://$host$request_uri; block.
  • Keep WebSocket support on. Audiobookshelf uses WebSockets for live updates. The sample configs in the ABS readme include the right Upgrade and Connection headers.
  • Pass through the X-Forwarded-Proto header. Audiobookshelf checks this to decide it’s behind HTTPS. Without it, the refresh-token cookie won’t get the Secure flag and OIDC redirect URLs can come back as http://. Every sample config in the ABS readme includes this, but worth checking if you wrote your own.

If you’re using Cloudflare, set SSL mode to Full (strict) in the dashboard. The “Flexible” setting only encrypts the visitor-to-Cloudflare leg and connects to your origin over HTTP, which defeats the point of the reverse proxy.

2. Keep the login page off the public internet

Audiobookshelf doesn’t support 2FA or passkeys. The maintainers closed the TOTP/passkey enhancement as “not planned”, so it’s not coming. There’s also no password-strength policy, so anyone can set their password to password1.

The server does have a built-in rate limiter on /login and /auth/refresh: 40 failed attempts per 10 minutes per IP (configurable via RATE_LIMIT_AUTH_MAX and RATE_LIMIT_AUTH_WINDOW env variables). That blocks casual brute-force from a single IP, but not a distributed attack, and it does nothing about weak passwords in the first place.

The right fix is to put another layer in front of the login page. Pick one:

Option A: Tailscale. Put your Audiobookshelf server on a Tailscale tailnet. Only devices logged into your Tailscale account can reach it. Zero public exposure. This is the strongest option for a personal server and takes about five minutes.

Option B: Cloudflare Access. If you’re already using Cloudflare, Cloudflare Access puts a Cloudflare login (Google, GitHub, Okta, email OTP) in front of your domain. Browser users hit that before they ever see the ABS login page. SoundLeaf supports this with service-token headers so the iPhone app still connects in the background.

Option C: Authelia or Authentik (advanced). If you already run your own auth stack, Authelia or Authentik can add TOTP, WebAuthn, or Duo on top of your reverse proxy. Works great but adds real operational complexity. Skip unless you already have it running.

If you want SSO in ABS itself, it supports OpenID Connect against Authelia, Authentik, Keycloak, Google, and similar. The catch is that you can’t fully disable password login while OIDC is on, so your local accounts still need strong passwords even after you set up SSO.

3. Tighten the built-in auth

You can leave the defaults alone and be fine for a personal server, but if you want to harden it further, a few things are worth tweaking.

Drop the rate limit tighter. The default of 40 attempts per 10 minutes is generous. For a private server, 10 is plenty:

# docker-compose.yml
services:
  audiobookshelf:
    environment:
      RATE_LIMIT_AUTH_MAX: "10"
      RATE_LIMIT_AUTH_WINDOW: "600000"   # 10 minutes in ms

Don’t set it to 0 on a public server, that disables the limiter entirely.

Behind a reverse proxy, the rate limiter keys off the client IP from X-Forwarded-For. If your proxy strips or doesn’t send that header, every request looks like it’s coming from the proxy’s IP and the limiter will lock out real users. Make sure your proxy config sets it.

Use API keys, not passwords, for automation. If you’re running scripts (rescans, metadata pulls, third-party integrations), don’t put your admin password in them. Create a dedicated user in Settings > Users, then generate an API key for that user in Settings > API Keys. Set an expiry. You can revoke a leaked key without changing your own password, and an expired key is a leaked-key that’s already time-bombed.

Prune stale users. Settings > Users. Delete accounts nobody uses. Every active user is an extra credential an attacker can try.

4. Run the Docker container as a non-root user

The official ghcr.io/advplyr/audiobookshelf image has no USER directive in its Dockerfile. Node runs as root inside the container. Container root isn’t host root, but any escape or misconfiguration is less bad if the process didn’t have root in the first place.

Run it as a specific UID/GID instead:

services:
  audiobookshelf:
    image: ghcr.io/advplyr/audiobookshelf:latest
    user: "1000:1000"   # whatever UID:GID owns /config and /metadata on the host
    volumes:
      - ./config:/config
      - ./metadata:/metadata
      - /path/to/audiobooks:/audiobooks:ro

Two things to line up:

  • Create the config and metadata directories on the host first, owned by that UID:GID. Otherwise ABS can’t write to them on first boot.

    mkdir -p config metadata
    sudo chown -R 1000:1000 config metadata
    
  • Mount the audiobook library read-only (the :ro flag) unless you actually use the upload-from-browser feature. Audiobookshelf doesn’t need to write to your library to scan or play. A read-only mount means a compromised ABS process can’t touch your files.

If you need uploads, you have to drop the :ro, but then lock down who has upload permission in Settings > Users.

5. Keep it updated

Audiobookshelf is a moving target. Issue #5182 flagged CVEs in bundled Node dependencies of the v2.33.x image (axios, form-data, libssh); the maintainer’s read is that most don’t materially impact ABS in practice, and the long-term fix is replacing axios with the native fetch API. What v2.33.2 (2026-04-19) actually shipped was real path-traversal, bulk-download IDOR, and backup-validation fixes, and those you do want.

Don’t use latest on a production server. You lose your ability to roll back when a release breaks something, and that happens. Pin a version in your compose file:

image: ghcr.io/advplyr/audiobookshelf:2.33.2

Then update on your own schedule:

docker compose pull
docker compose up -d

If you want notifications when a new release is tagged, Diun or Watchtower can watch image tags. I’d use them for notifications only, not for auto-applying updates.

6. Back up your config and metadata

Three things on the server matter:

  • /config: the SQLite database with users, password hashes (bcrypt), and settings
  • /metadata: covers, cached metadata, and backup archives
  • Your audiobook folder: the actual files

Audiobookshelf has a built-in backup at Settings > Backups that archives the database and metadata into /metadata/backups on a schedule. Good, but the backups sit on the same disk as the server, which is useless if the disk dies or the server gets compromised.

Copy them off the server regularly. A simple option with restic:

restic -r b2:your-bucket-name:abs-backups backup /path/to/metadata/backups

Or rsync them to another machine on the network, or anything else that gets them off the box. One thing to know: the backups contain bcrypt password hashes. Not plain-text passwords, but still worth encrypting in transit and at rest. Restic encrypts automatically, which is why I’d lean toward it over plain rsync for off-site backups.

What Audiobookshelf doesn’t give you

A few things are your responsibility because ABS doesn’t handle them:

  • 2FA/passkeys. Cover with Cloudflare Access, Tailscale, or Authelia.
  • Password strength enforcement. Cover by telling users to use a password manager, or via an OIDC provider that enforces it.
  • IP allowlisting. Enhancement #1269 is open. For now, restrict at your firewall or reverse proxy.
  • Audit logs for admin actions. Settings changes aren’t logged. If you have multiple admins and care, audit via the reverse proxy access logs.

If you’re using SoundLeaf, it supports Cloudflare Access service tokens so the iPhone app keeps working even with Zero Trust in front of ABS.