$linuxjunkies
>

Self-Host Kavita for Comics and Books

Deploy Kavita on Docker, configure comic and book libraries, enable OPDS for mobile reading apps, and secure it with an Nginx HTTPS reverse proxy.

IntermediateUbuntuDebianFedoraArch10 min readUpdated June 7, 2026

Before you start

  • Docker and Docker Compose v2 installed and working
  • A domain name with an A record pointing to your server's public IP
  • Ports 80 and 443 reachable from the internet (for Certbot and HTTPS)
  • Media files already transferred to the server in CBZ, EPUB, PDF, or similar formats

Kavita is a fast, self-hosted reading server for comics, manga, and ebooks. It speaks OPDS so any compatible reading app on your phone or tablet connects directly to your library. This guide walks through a Docker-based deployment, configuring libraries, enabling OPDS, and putting Kavita behind an Nginx reverse proxy with HTTPS.

Prerequisites and Directory Layout

You need Docker and Docker Compose installed, a domain name (or local hostname) pointing at your server, and a working Certbot or existing TLS certificate for HTTPS. Create a consistent directory structure before you touch any config:

sudo mkdir -p /opt/kavita/config
sudo mkdir -p /mnt/media/books
sudo mkdir -p /mnt/media/comics
sudo chown -R $USER:$USER /opt/kavita /mnt/media

Put your actual media wherever makes sense — NAS mount, second drive, etc. The paths above are just a reference. Kavita only needs read access to library directories, but the config volume needs read/write.

Deploy Kavita with Docker Compose

Using Compose keeps the container reproducible and easy to update. Create the Compose file:

nano /opt/kavita/docker-compose.yml

Paste the following, adjusting UID/GID to match your media-owning user (id -u and id -g will tell you):

services:
  kavita:
    image: jvmilazz0/kavita:latest
    container_name: kavita
    restart: unless-stopped
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=America/New_York
    volumes:
      - /opt/kavita/config:/kavita/config
      - /mnt/media/books:/books:ro
      - /mnt/media/comics:/comics:ro
    ports:
      - "5000:5000"

The :ro flag mounts media read-only — Kavita never needs to write to your library. Kavita's internal port is always 5000; the left side of ports is what hits the host. If 5000 is taken, change only the left number.

Start the container:

cd /opt/kavita
docker compose up -d
docker compose logs -f kavita

Wait for a line like Now listening on: http://[::]:5000, then press Ctrl+C to detach from the log stream.

Initial Setup and Admin Account

Open http://<server-ip>:5000 in a browser. Kavita will prompt you to create an admin account on first run — pick a strong password, you cannot recover it without editing the SQLite database directly.

After login, go to Admin Panel → General and set your server name. This name appears in OPDS feeds and reading apps.

Add and Scan Libraries

Libraries are the core organisational unit. Each library has a type (Book, Comic/Manga, or Mixed) that affects how metadata and reading progress work.

  1. Navigate to Admin Panel → Libraries → Add Library.
  2. Give it a name (e.g., Comics), choose the type, and add the folder path as Kavita sees it inside the container — /comics or /books, not the host path.
  3. Click Add. Kavita immediately starts a background scan.

Kavita expects files organised in folders. For comics: /comics/Publisher/Series Name/Series Name v01.cbz. For ebooks: /books/Author Name/Book Title/Book Title.epub. Flat directories work but metadata matching suffers. Supported formats include CBZ, CBR, CB7, CBT, PDF, EPUB, and raw image folders.

Monitor scan progress under Admin Panel → Tasks. Large libraries with many CBR files take longer because Kavita must inspect archives. Force a re-scan any time with the refresh icon beside each library.

Enable and Use OPDS

OPDS (Open Publication Distribution System) is the protocol that lets apps like Panels, Chunky, Moon+ Reader, or KOReader talk to Kavita.

Enable it in Admin Panel → General → Enable OPDS. The feed URL is:

http://<server-ip>:5000/api/opds/<api-key>

Find your API key under User Settings → Security → API Key. Each user has their own key, which means per-user access control over OPDS — a detail most self-hosted alternatives miss.

In your reading app, add a new OPDS catalog, paste the full URL including the key, and leave username/password blank (the key authenticates you). Once the reverse proxy is live, swap http://<server-ip>:5000 for your HTTPS domain.

Nginx Reverse Proxy with HTTPS

Exposing port 5000 directly to the internet is fine for testing but unacceptable in production. Nginx terminates TLS and forwards clean HTTP to the container.

Install Nginx

# Debian / Ubuntu
sudo apt install nginx -y

# Fedora / RHEL family
sudo dnf install nginx -y

# Arch
sudo pacman -S nginx

Obtain a Certificate

sudo apt install certbot python3-certbot-nginx -y   # Debian/Ubuntu
sudo certbot --nginx -d kavita.example.com

For Fedora/RHEL, replace apt install with dnf install. Certbot writes the certificate to /etc/letsencrypt/live/kavita.example.com/ and edits the Nginx config automatically. If you manage certs manually, reference them in the server block below.

Write the Server Block

sudo nano /etc/nginx/sites-available/kavita   # Debian/Ubuntu
# On Fedora/Arch, use /etc/nginx/conf.d/kavita.conf instead
server {
    listen 80;
    server_name kavita.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name kavita.example.com;

    ssl_certificate     /etc/letsencrypt/live/kavita.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/kavita.example.com/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;

    client_max_body_size 20M;

    location / {
        proxy_pass         http://127.0.0.1:5000;
        proxy_http_version 1.1;
        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_set_header   Upgrade           $http_upgrade;
        proxy_set_header   Connection        "upgrade";
        proxy_read_timeout 600s;
    }
}

The Upgrade and Connection headers are required for Kavita's SignalR real-time notifications (progress bars, scan updates). Without them, the UI still works but live updates silently fail.

Enable the Site and Reload

# Debian / Ubuntu only
sudo ln -s /etc/nginx/sites-available/kavita /etc/nginx/sites-enabled/kavita

# All distros
sudo nginx -t
sudo systemctl reload nginx

Open the Firewall

# ufw (Debian/Ubuntu)
sudo ufw allow 'Nginx Full'

# firewalld (Fedora/RHEL/Rocky)
sudo firewall-cmd --permanent --add-service=http --add-service=https
sudo firewall-cmd --reload

If you were previously testing with port 5000 open, close it now — traffic should only reach Kavita through Nginx:

# ufw
sudo ufw deny 5000

# firewalld
sudo firewall-cmd --permanent --remove-port=5000/tcp
sudo firewall-cmd --reload

Verification

Run through this checklist after everything is up:

  • Browse to https://kavita.example.com — valid cert, no browser warnings.
  • Log in, confirm libraries show the correct file and series counts.
  • Open a book or comic and verify the reader loads pages without errors in the browser console.
  • Paste your OPDS URL (now using the HTTPS domain) into a reading app and confirm the catalog populates.
  • Check that scan notifications appear in the UI in real time — if they don't, the WebSocket proxy headers are missing or wrong.

Keeping Kavita Updated

Kavita releases frequently. Pull the new image and recreate the container:

cd /opt/kavita
docker compose pull
docker compose up -d

Config and the SQLite database live in /opt/kavita/config and survive the update. Check the release notes before major version bumps — occasional migration steps are documented there.

Troubleshooting

Series not appearing after scan

Kavita requires at least one supported file inside a folder before it creates a series entry. Verify the path inside the container is correct (docker exec -it kavita ls /comics) and that the files are CBZ/EPUB/PDF, not RAR without a CBR extension. Kavita logs scan results to /opt/kavita/config/logs/.

OPDS returns 401 Unauthorized

The API key in the URL is user-specific and regenerates if you click Reset Key. Copy it fresh from User Settings → Security and update every reading app.

Blank page or console errors after reverse proxy

Kavita needs to know it is behind a proxy. Set the X-Forwarded-Proto header (already in the Nginx config above) and make sure no intermediate proxy strips it. If you are using Cloudflare, set SSL mode to Full (Strict).

Container restarts repeatedly

Run docker compose logs kavita --tail 50. A common cause is a permissions mismatch: the PUID/PGID in Compose does not own /opt/kavita/config. Fix with sudo chown -R 1000:1000 /opt/kavita/config (substitute your actual UID/GID).

tested on:Ubuntu 24.04Debian 12Fedora 40Arch rolling

Frequently asked questions

Which file formats does Kavita support?
Kavita reads CBZ, CBR, CB7, CBT, PDF, EPUB, and raw image folders (JPG/PNG inside a directory). RAR5-compressed CBR files require the host to have unrar available inside the container image; the official image ships with it.
Can multiple users share one Kavita instance with separate progress tracking?
Yes. Admins can create additional user accounts under Admin Panel → Users. Each user gets independent reading progress, bookmarks, and their own OPDS API key.
Does Kavita fetch metadata automatically?
Kavita can pull metadata from embedded ComicInfo.xml files inside CBZ archives and from EPUB metadata fields. For automatic online fetching you need to configure a metadata provider (such as AniList or MangaUpdates) under Admin Panel → Settings.
How do I move the media library to a new path without losing reading progress?
Update the Docker Compose volume mount to the new host path (keeping the same container-side path like /comics), then restart the container and run a forced library scan. Kavita matches series by folder and filename, so progress is preserved as long as the relative structure is unchanged.
Is Kavita suitable for running on a Raspberry Pi?
Yes. Kavita has an ARM64 image. On a Pi 4 or 5 with at least 2 GB RAM it runs acceptably for personal use, though initial scans of large comic libraries are slower than on x86 hardware.

Related guides