description: Deploy Audiobookshelf on a self-hosted Linux server — Docker setup, reverse proxy with nginx + Let's Encrypt, DuckDNS for dynamic DNS, and DNS challenge for SSL when ISP blocks ports 80/443.
Deploy Audiobookshelf via Docker with secure HTTPS access — including reverse proxy, dynamic DNS, and SSL via Let's Encrypt DNS challenge (for residential ISPs that block ports 80/443).
## When to Use
- User asks to install Audiobookshelf
- User wants a self-hosted audiobook/podcast server
- User needs HTTPS access to a self-hosted service behind a residential ISP
- User needs a reverse proxy pattern for any Docker-hosted web service
- **`network_mode: host`** — Audiobookshelf defaults to port 80 internally. Set `PORT=13378` to move it off port 80 (needed for nginx later). With host networking, the port binds directly on the host.
- **Audiobook mount** — Use `:ro` (read-only). Audiobookshelf only reads files; no write access needed.
- The container needs ~5-10 seconds on first run to initialize its SQLite database and generate a JWT secret.
### 3. Start and verify
```bash
cd ~/docker/audiobookshelf && docker compose pull && docker compose up -d
**Why DNS challenge:** Residential ISPs often block incoming ports 80/443. Let's Encrypt HTTP challenge requires port 80. DNS challenge works without any open ports — it adds a TXT record to the domain.
```bash
# Create credentials file
sudo mkdir -p /etc/letsencrypt
sudo tee /etc/letsencrypt/duckdns.ini << 'EOF' > /dev/null
- **Android** — [Audiobookshelf on Google Play](https://play.google.com/store/apps/details?id=com.audiobookshelf.app)
Connect the app to `https://<domain>.duckdns.org:3443` with your account. Supports offline downloads, sleep timer, and playback speed controls.
## Pitfalls
- **Port 80 conflicts with host networking** — Audiobookshelf defaults to port 80 internally. With `network_mode: host`, this binds directly on the host, conflicting with nginx. Set `PORT=13378` in the environment to move it before nginx setup.
- **Docker port conflicts** — Other containers may already use ports you want for nginx (e.g., Heimdall on 8443). Check with `docker ps --format '{{.Names}} {{.Ports}}'` before picking an nginx port.
- **Shell mangling of tokens** — If API tokens or Docker env vars contain special chars (underscores, dashes), bash may mangle them in variable expansion. Use Python for HTTP calls with tokens: `urllib.request.Request(url)` with `headers` works reliably. See `plex-setup` for the same pattern.
- **Hairpin NAT** — Testing the public URL from inside the LAN often fails (connection refused) even when the setup is correct. Always test from a mobile device on cellular data, not Wi‑Fi.
- **`certbot-dns-duckdns` not in apt** — On Ubuntu 26.04, this package isn't in apt. Use `pip3 install --break-system-packages certbot-dns-duckdns` instead.
- **Audiobookshelf first-run delay** — The container needs ~10 seconds to initialize its SQLite database, generate a JWT secret, and start listening. Don't assume it's broken if port 13378 isn't immediately available.
- **`docker compose restart` vs `up -d`** — `restart` reuses the old container config (doesn't pick up new env vars like `PORT`). Use `up -d` when changing environment variables, then `restart` for simple process cycling.
## Verification Checklist
- [ ]`curl http://localhost:13378/` returns 200
- [ ] DuckDNS updater returns "OK" on manual run
- [ ]`dig +short <domain>.duckdns.org` returns the correct public IP
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.