8.6 KiB
name, description, version, author, license, platforms, metadata, related_skills
| name | description | version | author | license | platforms | metadata | related_skills | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| audiobookshelf-setup | 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. | 1.0.0 | Hermes Agent | MIT |
|
|
|
Audiobookshelf Setup
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
Prerequisites
- Docker + docker-compose-v2 installed
- A domain or DuckDNS subdomain (for SSL)
- Port forwarding in router (for external access)
Setup Steps
1. Create directory structure
mkdir -p ~/docker/audiobookshelf/{config,metadata}
2. Create docker-compose.yml
services:
audiobookshelf:
image: ghcr.io/advplyr/audiobookshelf:latest
container_name: audiobookshelf
network_mode: host
environment:
- TZ=America/New_York
- PORT=13378
volumes:
- /home/ray/docker/audiobookshelf/config:/config
- /home/ray/docker/audiobookshelf/metadata:/metadata
- /path/to/audiobooks:/audiobooks:ro
restart: unless-stopped
Key points:
network_mode: host— Audiobookshelf defaults to port 80 internally. SetPORT=13378to 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
cd ~/docker/audiobookshelf && docker compose pull && docker compose up -d
sleep 10
curl -s -o /dev/null -w "HTTP %{http_code}" http://localhost:13378/
# Should return 200
4. Set up DuckDNS (free dynamic DNS)
If using a residential ISP without a static IP:
- Go to https://duckdns.org → sign in with GitHub/Google
- Create a subdomain (e.g.,
mybooks) - Copy the token from the top of the page
Install the updater script and cron job:
sudo mkdir -p /opt/duckdns
sudo tee /opt/duckdns/update.sh << 'EOF' > /dev/null
#!/bin/bash
echo url="https://www.duckdns.org/update?domains=<YOUR_SUBDOMAIN>&token=<YOUR_TOKEN>&ip=" | curl -k -s -o /opt/duckdns/duck.log -K -
EOF
sudo chmod +x /opt/duckdns/update.sh
# Run once to register
sudo /opt/duckdns/update.sh && cat /opt/duckdns/duck.log
# Should output "OK"
# Auto-update every 5 minutes
(sudo crontab -l 2>/dev/null | grep -v duckdns; echo "*/5 * * * * /opt/duckdns/update.sh") | sudo crontab -
Verify the domain resolves: dig +short <subdomain>.duckdns.org
5. Install nginx + certbot
sudo apt-get update && sudo apt-get install -y nginx certbot python3-certbot-nginx
# Install DuckDNS plugin for Let's Encrypt DNS challenge
sudo pip3 install --break-system-packages certbot-dns-duckdns
6. Get SSL certificate via DNS challenge
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.
# Create credentials file
sudo mkdir -p /etc/letsencrypt
sudo tee /etc/letsencrypt/duckdns.ini << 'EOF' > /dev/null
dns_duckdns_token=<YOUR_TOKEN>
EOF
sudo chmod 600 /etc/letsencrypt/duckdns.ini
# Get certificate
sudo certbot certonly \
--authenticator dns-duckdns \
--dns-duckdns-credentials /etc/letsencrypt/duckdns.ini \
--dns-duckdns-propagation-seconds 30 \
-d <domain>.duckdns.org \
--non-interactive --agree-tos --email <your-email>
Cert will auto-renew via certbot's built-in systemd timer. No cron needed.
7. Configure nginx reverse proxy with SSL
Pick an available high port (since 80/443 are blocked by ISP). Check what's free:
sudo ss -tlnp | grep -E "3443|4443|5443"
Create the nginx config:
server {
listen 3443 ssl;
server_name <domain>.duckdns.org;
ssl_certificate /etc/letsencrypt/live/<domain>.duckdns.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/<domain>.duckdns.org/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
client_max_body_size 500M;
location / {
proxy_pass http://127.0.0.1:13378;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 86400;
}
}
# Optional: redirect HTTP → HTTPS
server {
listen 80;
server_name <domain>.duckdns.org;
return 301 https://$host:3443$request_uri;
}
Enable and reload:
sudo cp audiobookshelf.conf /etc/nginx/sites-available/audiobookshelf
sudo ln -sf /etc/nginx/sites-available/audiobookshelf /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginx
8. Port forwarding
Forward the chosen high port (e.g., 3443 TCP) in your router to the server's LAN IP.
9. Access and create library
- Visit
https://<domain>.duckdns.org:3443 - Create an admin account on first visit
- Add a library → point it to
/audiobooks(the container's internal mount path) - Audiobookshelf will scan and populate automatically
10. Client apps
- Web player — built-in, at the same URL
- iOS — Audiobookshelf App
- Android — Audiobookshelf on Google Play
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. SetPORT=13378in 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)withheadersworks reliably. Seeplex-setupfor 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-duckdnsnot in apt — On Ubuntu 26.04, this package isn't in apt. Usepip3 install --break-system-packages certbot-dns-duckdnsinstead.- 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 restartvsup -d—restartreuses the old container config (doesn't pick up new env vars likePORT). Useup -dwhen changing environment variables, thenrestartfor simple process cycling.
Verification Checklist
curl http://localhost:13378/returns 200- DuckDNS updater returns "OK" on manual run
dig +short <domain>.duckdns.orgreturns the correct public IP- SSL cert obtained (
sudo certbot certificates) - nginx config passes
sudo nginx -t curl -sk https://localhost:3443/returns 200 (SSL working locally)- Port forwarding rule added in router
- External access confirmed from cellular data
Support Files
templates/docker-compose.yml— Starter Docker Compose filetemplates/nginx-audiobookshelf.conf— nginx reverse proxy config with SSLscripts/duckdns-update.sh— DuckDNS IP update script (set DOMAIN and TOKEN)references/heimdall-dashboard.md— Add the service to Heimdall dashboard via SQLite