All articles
TutorialsJul 21, 2026 · 24 min read · By The RDP.sh Team

Self-Host Kavita on a VPS for Your Own Ebook and Comic Library

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.

Kavita reads your files but never rewrites them in place. Its entire library structure - series, volumes, chapters - is inferred from your folder and file names. Get the naming right before the first scan and everything just works. Get it wrong and you will be renaming files and re-scanning to untangle a mess.

TL;DR

  • Install Docker and Docker Compose on a fresh VPS
  • Point a subdomain like books.example.com at your server
  • Run Kavita and Caddy from a single docker-compose.yml
  • Mount two volumes: config (database and settings) and library (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 80 and 443 open 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 logs
  • library/ 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 library volume 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 5000 stays on the booknet Docker network. Caddy reaches it by container name, so it never touches the public internet directly.
  • Set TZ to 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.

Right after setup, open Settings -> General and check that the server base URL matches 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. Manga reads right-to-left, Comic left-to-right, Book uses the EPUB reader.
  • Add the folder, picking the container path like /library/comics or /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 Books but 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:

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.xml file 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 config folder 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.