Self-Host Kavita on a VPS for Your Own Ebook and Comic Library
If your ebooks live in a Calibre folder on one laptop and your comics sit in a pile of CBZ files you can only open at a desk, you already know the problem: your reading library is stuck to one machine. Meanwhile the commercial ebook stores lend you licenses that can be revoked, sync your highlights to their servers, and lock your purchases to their apps. Kavita is the fix. It is a fast, self-hosted reading server that turns a folder of EPUBs, PDFs, CBZ comics, and manga into a polished web reader with a proper library, reading progress that follows you across devices, and support for standard mobile reading apps through OPDS.
This guide walks through a production-ready Kavita install on a VPS using Docker, with Caddy in front for automatic HTTPS. It covers the parts that actually trip people up: how to lay out your folders so Kavita scans them cleanly, how OPDS unlocks the reading apps you already use, and how to add family members without handing them the keys to the whole server.
TL;DR
- Install Docker and Docker Compose on a fresh VPS
- Point a subdomain like
books.example.comat your server - Run Kavita and Caddy from a single
docker-compose.yml - Mount two volumes:
config(database and settings) andlibrary(your books, read-only) - Caddy handles HTTPS automatically
- Create the admin account on first run, then add libraries and let the scanner index them
- Connect any OPDS-capable reading app on mobile and read from anywhere
Total time: about 15 minutes.
What You Need
- A VPS with 1 GB RAM (Kavita is a lightweight .NET app and idles low)
- Disk space for your books - text libraries are tiny, but large comic and manga collections with full-color scans get big, so a storage VPS is worth it past a few thousand issues
- A domain you can add DNS records to
- Ports
80and443open to the internet (Let's Encrypt needs them) - Root or sudo access
Kavita is one of the lighter media servers you can run. The database is SQLite, serving a page is cheap, and the only mildly heavy operations are the initial scan and cover-image extraction. Disk for the books themselves is the real sizing question - budget accordingly if you keep high-resolution scans.
Step 1: Point a Subdomain at Your VPS
In your DNS provider, create an A record:
books.example.com → YOUR_VPS_IPV4
Add an AAAA record too if your server has IPv6. Confirm it resolves:
dig +short books.example.com
The output should be your VPS IP. Caddy cannot issue a certificate until DNS points at the box.
Step 2: Install Docker and Docker Compose
On a fresh Ubuntu 22.04 or 24.04 server:
sudo apt update
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
-o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Check it works:
docker --version
docker compose version
Step 3: Open the Firewall
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
Caddy needs 80 for the ACME HTTP challenge and 443 for HTTPS. Do not expose Kavita's internal port 5000 to the internet - Caddy is the only thing that should answer publicly.
Step 4: Create the Project Directory
sudo mkdir -p /opt/kavita
cd /opt/kavita
sudo mkdir -p config library caddy-data caddy-config
The two data directories map to how Kavita splits state:
config/holds the SQLite database, settings, extracted cover images, cache, and logslibrary/is where your books live, mounted read-only
If your VPS has a large data disk mounted elsewhere, point the library folder at it:
sudo mkdir -p /mnt/storage/books
sudo ln -s /mnt/storage/books /opt/kavita/library
Step 5: Lay Out Your Files So Kavita Scans Them Cleanly
Kavita builds its library from folder and file names, so a consistent layout is the single most important thing you do. Kavita groups content by library type, and each type expects a slightly different structure. The reliable pattern is one folder per series, with volumes and chapters encoded in the file names.
For a manga or comic library:
library/
comics/
Saga/
Saga Vol. 1.cbz
Saga Vol. 2.cbz
Invincible/
Invincible #001.cbz
Invincible #002.cbz
For an ebook library:
library/
books/
Andy Weir/
Project Hail Mary.epub
Ursula K. Le Guin/
The Dispossessed.epub
Kavita understands Vol., Volume, Ch., Chapter, and issue-number patterns like #001. EPUB files carry their own metadata, so Kavita reads title, author, and series straight from the file - naming matters most for comics and manga where the container format has no rich metadata. Keep one series per folder and you avoid almost every scan headache.
Kavita reads EPUB, PDF, CBZ, CBR, CB7, CBT, and plain images. It never modifies your files, which is exactly why the library volume is mounted read-only later.
Step 6: Write the Compose File
Create /opt/kavita/docker-compose.yml:
services:
kavita:
image: jvmilazz0/kavita:latest
container_name: kavita
restart: unless-stopped
environment:
TZ: "Europe/Berlin"
volumes:
- ./config:/kavita/config
- ./library:/library:ro
networks:
- booknet
caddy:
image: caddy:2
container_name: kavita-caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./caddy-data:/data
- ./caddy-config:/config
networks:
- booknet
networks:
booknet:
Notes:
- The
libraryvolume is mounted:ro(read-only) on purpose. Kavita never needs to write to your books, and a read-only mount means a bug or a bad actor cannot delete your collection. - When you add a library later in the web UI, point it at
/library/comics,/library/books, and so on - those are the paths inside the container. - The internal port
5000stays on thebooknetDocker network. Caddy reaches it by container name, so it never touches the public internet directly. - Set
TZto your zone so reading history timestamps and scheduled scans line up with your clock.
Step 7: Write the Caddyfile
Create /opt/kavita/Caddyfile:
books.example.com {
encode zstd gzip
reverse_proxy kavita:5000 {
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
header_up Host {host}
}
}
The X-Forwarded-Proto header is the one that matters most. Without it Kavita can build http:// links internally, which breaks OPDS clients and the invite links you send to new users. Caddy handles the certificate, renewal, and HTTP-to-HTTPS redirect with no extra config.
Step 8: Start the Stack
cd /opt/kavita
sudo docker compose up -d
sudo docker compose logs -f
Wait until the Caddy logs show the Let's Encrypt certificate was issued, then open https://books.example.com. You should land on the Kavita first-run setup screen.
Step 9: Create the Admin Account
The first screen asks you to register the admin user. This is the all-powerful account, so pick a strong password and a real email address - Kavita uses email for password resets and for inviting other users. Once you are in, you land on an empty dashboard ready for its first library.
https://books.example.com. If it still shows an internal address or plain http, invite links and OPDS URLs will point somewhere unreachable. This is the number-one cause of "the app cannot connect" reports.
Step 10: Add Your Libraries
In the admin dashboard, go to Settings -> Libraries -> Add Library. For each one:
- Give it a name (
Comics,Books,Manga). - Choose the matching library type - this controls how Kavita parses names and which reader it uses.
Mangareads right-to-left,Comicleft-to-right,Bookuses the EPUB reader. - Add the folder, picking the container path like
/library/comicsor/library/books.
Save it and Kavita immediately starts scanning in the background. You can watch progress in the logs:
sudo docker compose logs -f kavita
A few hundred books index in under a minute. Large comic collections take longer because Kavita extracts a cover image from every file. Let the first scan finish before you decide whether anything is missing.
Step 11: Add Users Without Giving Away the Server
Kavita has real multi-user support with per-user reading progress, bookmarks, and want-to-read lists. Invite people under Settings -> Users -> Invite:
- Enter their email and choose which libraries they can see - you can give a child access to
Booksbut not the rest. - Leave the admin role off for everyone who is not you. Regular users read, track progress, and bookmark, but cannot change server settings, add libraries, or manage other accounts.
- Kavita generates an invite link. If you have not configured an email server, copy that link and send it yourself - the user sets their own password from it.
Each account keeps its own reading position, so your progress through a series never collides with anyone else's.
Step 12: Read on Mobile with OPDS
This is where a self-hosted library pays off. Kavita speaks OPDS, the open catalog standard supported by a wide family of reading apps, so you are not locked into one official client. Enable it first under Settings -> General -> Enable OPDS, then grab your personal feed URL from your user account page (Settings -> Account -> 3rd Party Clients / OPDS URL). It looks like:
https://books.example.com/api/opds/YOUR-API-KEY
Paste that URL into an OPDS-capable reader:
- iOS / iPadOS: Panels or Chunky for comics, KyBook or Marvin for ebooks
- Android: Moon+ Reader, Librera, or Tachiyomi-style manga readers with an OPDS extension
- Desktop / e-ink: Thorium Reader and KOReader both connect over OPDS
The API key in the URL is effectively a password, so treat it like one. If it leaks, regenerate it from your account page and every old link stops working immediately.
Step 13: Fix Metadata and Covers
Comics and manga carry little embedded metadata, so Kavita occasionally guesses a series name or grabs an ugly cover. You can correct it without touching the files:
- On a series page, click the pencil to edit its name, sort name, and summary, or lock fields so a future scan does not overwrite your edits.
- Hover a cover and choose Change cover to pick a different page from inside the file or upload your own image.
- For a permanent fix that survives re-imports, add a
ComicInfo.xmlfile inside a CBZ - Kavita reads it for accurate series, volume, and writer metadata.
Locking a field is the trick most people miss. Once locked, your manual title survives every rescan.
Step 14: Back Up the Database
Your book files are presumably backed up already. The piece unique to Kavita - user accounts, reading progress, bookmarks, collections, and metadata edits - lives in the config directory. Losing it means every family member starts their series over from page one. Pair it with a nightly restic job to object storage:
restic -r s3:s3.amazonaws.com/my-backup-bucket backup /opt/kavita/config
Kavita also writes its own periodic database backups into config/backups/, which the restic job sweeps up automatically. The directory is small - usually well under a gigabyte - so backups are fast and cheap. Restoring is just dropping the folder back and starting the container.
Optional: Keep It Private with a VPN
If the only people using your server live in your house, it does not need to face the public internet at all. Putting Kavita behind Tailscale removes every bot scan and login attempt in one move. Skip the firewall openings for 80 and 443, reach books.example.com over your tailnet, and OPDS apps connect exactly the same way. A WireGuard VPN on your VPS works just as well if you already run one.
Troubleshooting
Caddy returns a 502 right after docker compose up. Kavita takes a few seconds to build its database on first boot, and Caddy can hit it first. Wait 30 seconds and reload. If it sticks, check docker compose logs kavita.
The library is empty after a scan. Almost always a path or permission issue. Confirm the folder you added in the UI matches a real path under /library, and that the container can read it: docker exec -it kavita ls -la /library. If you see permission denied, fix ownership on the host with sudo chown -R 1000:1000 /opt/kavita/library.
Series are split, merged, or named wrong. This is a naming problem, not a Kavita bug. Keep one series per folder, use consistent Vol. and #001 patterns, and re-run the scan. For comics, a ComicInfo.xml inside the CBZ removes the guesswork entirely.
An OPDS app says it cannot connect with the right URL. Confirm OPDS is enabled in settings and that the server base URL is the public https:// hostname, not an internal address. Copy the OPDS URL fresh from your account page - a regenerated API key invalidates every old link.
Reading progress is not syncing between devices. Progress is tied to your user account, so make sure both devices log in as the same user. In OPDS apps that only download files, progress lives in the app, not on the server - use the Kavita web reader or an app that reports progress back for cross-device sync.
Going Further
- Add rich comic metadata. Tag your CBZ files with a tool like ComicTagger so every issue carries a
ComicInfo.xml. Kavita then shows accurate writers, publishers, and release dates with zero manual editing. - Set up reading lists and collections. Group a crossover event or a themed shelf into a collection that everyone on the server can browse, independent of folder structure.
- Run it next to your other media. If you already self-host Jellyfin for video or Navidrome for music, Kavita rounds out the stack on the same box behind the same Caddy instance - just add another site block.
- Automate offsite backups. Wire the
configfolder into a nightly restic job so a dead disk never costs you your accounts and reading history.
That's it. Self-hosted Kavita gives you a fast, private reading library built from books and comics you actually own, reachable from any device, with no subscription, no revoked licenses, and no reading data handed to a store.
Need a VPS for your reading library? Our Linux plans ship with NVMe storage, generous bandwidth, and a storage tier for big comic and manga collections. See the options.