Komga is a self-hosted media server for comics, manga, BDs, magazines, and ebooks. It reads CBZ, CBR, PDF, and EPUB, imports metadata embedded in ComicInfo.xml and EPUB OPF files, organizes series into collections and read lists, and serves everything through a responsive web reader, a REST API, OPDS v1 and v2 feeds, and native Kobo sync. It is written in Kotlin on the JVM, which makes it heavier at idle than some alternatives but very stable under large libraries.
This guide deploys Komga on a single RamNode VPS with Docker Compose, a dedicated service user, an explicit JVM heap limit, Nginx terminating TLS, and the container bound to localhost.
Architecture Overview
- Komga container. A Spring Boot application serving the web UI, the REST API, OPDS feeds, Kobo sync endpoints, and a Server-Sent Events stream that pushes scan progress and library changes to open browser tabs.
/config. Holds Komga's SQLite databases, the Lucene search index, logs, and the optionalapplication.ymlconfiguration file./data. The library root inside the container. This guide mounts/srv/media/comicsthere. You can add more mounts for separate libraries.- Nginx. Handles TLS and proxies to
127.0.0.1:25600. It needs buffering disabled for the event stream and larger header buffers if you use Kobo sync.
Komga uses SSE rather than WebSockets. A proxy that buffers responses holds those events back, so the UI looks frozen during scans even though Komga is working. Step 6 handles this.
What You Will Need
- A RamNode VPS running Ubuntu 24.04 LTS with at least 2 GB RAM and 1 vCPU. 4 GB is more comfortable for libraries in the tens of thousands of books, or if Komga shares the box with other services. The JVM will use whatever heap it is allowed, so you set a limit explicitly in Step 4.
- Disk sized for your library. If your plan supports additional block storage volumes, put media there.
- Root or sudo access.
- A domain or subdomain with an A record pointing at the VPS. This guide uses
komga.example.com.
Step 1: Prepare the System
apt update && apt upgrade -y
apt install -y ca-certificates curl gnupg ufw rsync
timedatectl set-timezone America/New_YorkAdd swap. On a 2 GB plan this is not optional. A JVM heap plus page cache plus a large first scan will otherwise trigger the OOM killer.
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstabOptional: Mount a Block Storage Volume for Media
lsblk
mkfs.ext4 /dev/vdb
mkdir -p /srv/media
echo "UUID=$(blkid -s UUID -o value /dev/vdb) /srv/media ext4 defaults,nofail 0 2" >> /etc/fstab
mount -a
df -h /srv/mediaKeep /opt/komga/config on the root disk. SQLite and the Lucene index both expect local storage.
Step 2: Install Docker
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
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" \
> /etc/apt/sources.list.d/docker.list
apt update
apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
systemctl enable --now dockerCap container log size.
cat > /etc/docker/daemon.json <<'EOF'
{
"log-driver": "json-file",
"log-opts": { "max-size": "10m", "max-file": "3" }
}
EOF
systemctl restart dockerStep 3: Create the Service User and Directories
Komga's image supports running as an arbitrary UID through the Compose user: directive. Run it as a dedicated system user rather than root.
useradd --system --no-create-home --shell /usr/sbin/nologin komga
mkdir -p /opt/komga/{config,tmp}
mkdir -p /srv/media/comics
chown -R komga:komga /opt/komga /srv/media/comicsWrite the IDs to an env file for Compose.
cat > /opt/komga/.env <<EOF
KOMGA_UID=$(id -u komga)
KOMGA_GID=$(id -g komga)
TZ=$(timedatectl show -p Timezone --value)
EOF
cat /opt/komga/.envStep 4: Write the Compose File
pico /opt/komga/compose.yamlservices:
komga:
image: gotson/komga:latest
container_name: komga
user: "${KOMGA_UID}:${KOMGA_GID}"
environment:
- TZ=${TZ}
- JAVA_TOOL_OPTIONS=-Xmx1g
ports:
- "127.0.0.1:25600:25600"
volumes:
- /opt/komga/config:/config
- /opt/komga/tmp:/tmp
- /srv/media/comics:/data
restart: unless-stoppedWhat each choice does:
JAVA_TOOL_OPTIONS=-Xmx1g. Caps the JVM heap. Use1gon a 2 GB plan,2gon 4 GB. If you seeOutOfMemoryErrorin the logs during scans, raise it. Leaving it unset lets the JVM size the heap from total RAM, which leaves too little for the OS and page cache on small plans./opt/komga/tmp:/tmp. Komga extracts archive pages to temp during analysis and thumbnail generation. Mapping it to the host keeps that I/O off the container's overlay layer.127.0.0.1:25600:25600. Docker's port publishing bypasses UFW. Binding to loopback keeps the port private to Nginx.- Library mount is read-write. Komga only writes to library files if you enable features that need it, such as deleting duplicate pages, removing empty directories, or writing metadata back. If you will never use those, add
:roto the/datamount. - Pin a version. For production, replace
latestwith a specificx.y.ztag from Docker Hub or the GitHub releases page.
Start it.
cd /opt/komga
docker compose up -d
docker compose logs -fThe first start takes noticeably longer than later ones while the database initializes. Wait for the Spring Boot "Started" line, exit the log stream, and check it locally.
curl -sI http://127.0.0.1:25600 | head -1Step 5: Configure the Firewall
apt install -y nginx certbot python3-certbot-nginx
ufw allow OpenSSH
ufw allow 'Nginx Full'
ufw enable
ufw statusStep 6: Configure the Nginx Reverse Proxy
pico /etc/nginx/sites-available/komgaserver {
listen 80;
listen [::]:80;
server_name komga.example.com;
client_max_body_size 500M;
# Kobo devices send very large request headers during sync.
# Default buffers reject them with 502 or 400 errors.
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
large_client_header_buffers 4 32k;
location / {
proxy_pass http://127.0.0.1:25600;
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 X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
# Server-Sent Events. Without this, scan progress and
# library updates arrive in bursts or not at all.
proxy_buffering off;
proxy_cache off;
proxy_set_header Connection '';
proxy_read_timeout 1h;
}
}The X-Forwarded-* headers matter more for Komga than most apps. Komga builds absolute URLs in OPDS feeds and Kobo sync responses from them. If they are missing, reader apps receive http://127.0.0.1:25600/... links that do not resolve.
Enable the site and issue a certificate.
ln -s /etc/nginx/sites-available/komga /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default
nginx -t && systemctl reload nginx
certbot --nginx -d komga.example.com --redirect -m you@example.com --agree-tos --no-eff-email
systemctl list-timers | grep certbotStep 7: Optional application.yml
Komga reads /config/application.yml at startup. Most settings live in the web UI, but a few are only configurable here. The useful one on a public server is a fixed remember-me key, so "remember me" logins survive container restarts.
pico /opt/komga/config/application.ymlkomga:
remember-me:
key: replace-with-output-of-openssl-rand-hex-32
validity: 2592000 # 30 days, in secondsopenssl rand -hex 32
chown komga:komga /opt/komga/config/application.yml
cd /opt/komga && docker compose restartStep 8: First-Run Setup
Browse to https://komga.example.com. The first visit asks you to create the initial account, which becomes the admin. Use a strong password.
Add a library from the + next to Libraries in the sidebar:
- Name it and set the root folder to
/data, the container path. - Review the scanner options. Scan on startup and the periodic scan interval control how quickly new files appear. For a library you add to often, a shorter interval works. For a mostly static one, daily is plenty.
- Under metadata, enable import of
ComicInfo.xmland EPUB metadata if your files carry it. - Save. Komga scans immediately, then analyzes each book to count pages and generate thumbnails. Analysis is the slow part on large libraries.
Create non-admin users under Server > Users. Each user can be limited to specific libraries, age ratings, and labels.
Step 9: Load Your Library
Komga treats every folder that directly contains books as a series. Nest as deep as you like for your own organization.
/srv/media/comics/
├── Marvel/
│ └── Saga of the Swamp Thing (1982)/
│ ├── Saga of the Swamp Thing 001 (1982).cbz
│ └── Saga of the Swamp Thing 002 (1982).cbz
└── Manga/
└── Vinland Saga/
├── Vinland Saga v01.cbz
└── Vinland Saga v02.cbzHere Saga of the Swamp Thing (1982) and Vinland Saga are series. Marvel and Manga are just folders. Books sort by filename unless ComicInfo.xml supplies a number.
Upload with rsync and fix ownership.
rsync -avP --partial ~/Comics/ root@your-vps-ip:/srv/media/comics/
chown -R komga:komga /srv/media/comicsTrigger a scan from the library menu or wait for the scheduled one.
Step 10: OPDS, API Keys, and Kobo Sync
OPDS. Komga serves two feeds:
https://komga.example.com/opds/v1.2/catalog
https://komga.example.com/opds/v2/catalogMost reader apps use v1.2. Authenticate with your Komga username and password, or with an API key generated under your account settings, which you can revoke without changing your password.
Kobo sync. Komga can act as a sync server for Kobo e-readers, pushing EPUBs from read lists to the device and syncing progress back. Enable it per user in the account settings, follow the on-screen instructions to point the device's sync endpoint at your Komga URL, and keep the larger header buffers from Step 6 in place. Kobo sync is the feature most sensitive to proxy misconfiguration, so test it with one book before relying on it.
Backups
Stop the container for a consistent copy of the databases and search index.
cd /opt/komga
docker compose stop
tar czf /root/komga-config-$(date +%F).tar.gz config
docker compose startAutomate it with a cron entry and copy the archives off the server.
cat > /etc/cron.d/komga-backup <<'EOF'
30 3 * * * root cd /opt/komga && docker compose stop >/dev/null && tar czf /root/komga-config-$(date +\%F).tar.gz config && docker compose start >/dev/null && find /root -name 'komga-config-*.tar.gz' -mtime +14 -delete
EOFThe downtime is a few seconds plus JVM startup. The library is not included. Keep originals elsewhere.
Upgrading
cd /opt/komga
docker compose pull
docker compose up -d
docker image prune -fIf you pinned a tag, update compose.yaml first. Komga runs database migrations on startup that cannot be reversed. Back up before every upgrade and read the release notes, especially across major versions.
Troubleshooting
Container exits or restarts during a large scan. Check docker compose logs --tail=200 for OutOfMemoryError. Raise -Xmx, make sure swap is active with swapon --show, and if needed move to a larger plan. Also check dmesg | grep -i oom for the kernel killing the container from outside the JVM.
Scan progress never updates in the browser. SSE is being buffered. Confirm proxy_buffering off is present in the server block Certbot edited: nginx -T | grep proxy_buffering.
OPDS clients get broken links pointing at 127.0.0.1. The X-Forwarded-* headers are missing. Add them, reload Nginx, and re-add the feed in the reader app.
Kobo sync fails with 502 or "request header too large". Increase the header buffer sizes in Step 6. Watch /var/log/nginx/error.log during a sync attempt to see which limit is being hit.
Files show as "Unknown" or with zero pages. The book failed analysis. Usually a corrupt archive or an unsupported format inside the archive. Check the book's detail page for the error, and test the file locally.
Permission errors in the logs. The library or config directory is not owned by the komga UID. Rerun the chown commands from Step 3 and Step 9.
Hardening Notes
- Confirm loopback binding with
ss -tlnp | grep 25600. - Use API keys rather than your password for OPDS clients, and revoke them when a device is retired.
- Mount the library read-only unless you use Komga's file-modifying features.
- Use SSH keys and disable password login.
- For personal use, consider WireGuard or Tailscale in front and no public DNS record.
Next Steps
Komga and Kavita overlap heavily. Komga is the stronger choice for Western comics with rich ComicInfo.xml metadata and for Kobo owners. Kavita is lighter on RAM and has a more polished EPUB reader. Both have their own RamNode guides, and you can point both at the same library to compare before committing. Add Audiobookshelf on its own subdomain to cover audiobooks and podcasts from the same VPS.
