Ebook Library
    Python

    Deploy Calibre-Web on a VPS

    Install Calibre-Web 0.6.27 on a RamNode VPS in a Python virtualenv with systemd, Calibre CLI tools, Nginx TLS, Kobo sync, and nightly backups.

    Calibre-Web is a lightweight web front end for a Calibre ebook library. It gives you a clean browser interface for browsing, reading, and downloading books, plus OPDS feeds for reader apps, Kobo device sync, send-to-eReader, multi-user accounts with per-user shelves, and in-browser metadata editing. It reads and writes a standard Calibre metadata.db, so the same library stays compatible with desktop Calibre.

    This guide installs Calibre-Web 0.6.27 natively in a Python virtual environment on Ubuntu 24.04 LTS, runs it under a dedicated systemd service account, and publishes it behind Nginx with a Let's Encrypt certificate.

    What You Will Build

    • Calibre-Web 0.6.27 in an isolated virtualenv at /opt/calibre-web
    • A Calibre library at /srv/calibre/library
    • Calibre's command-line tools (calibredb, ebook-convert) for library creation and format conversion
    • A hardened systemd unit listening only on 127.0.0.1:8083
    • Nginx reverse proxy with TLS, upload limits sized for ebooks, and Kobo-friendly proxy buffers
    • Nightly backups of the library and the Calibre-Web settings database

    Server Sizing

    Calibre-Web itself is light. Format conversion through ebook-convert is what drives CPU and RAM usage.

    Use casevCPURAMDiskNotes
    Personal library, browsing and OPDS only11 GB20 GBAdd 1 GB swap; skip heavy conversions
    Household library with conversion and Kobo sync22 GB40 GB+Recommended starting point
    Large library (20k+ titles) or several active users2 to 44 GB100 GB+Covers and conversions add up fast

    Disk is usually the deciding factor. Budget the size of your existing Calibre folder plus roughly 25% headroom for covers, converted formats, and backups.

    Prerequisites

    • A RamNode VPS running Ubuntu 24.04 LTS
    • Root or sudo access over SSH
    • A DNS A record (and AAAA if you use IPv6) pointing a hostname such as books.example.com at the VPS
    • An existing Calibre library to import (optional; this guide also creates an empty one)

    Replace books.example.com throughout with your hostname.

    Step 1: Prepare the System

    Update packages and install base tooling:

    shell
    sudo apt update && sudo apt -y full-upgrade
    sudo apt -y install python3 python3-venv python3-dev build-essential \
      sqlite3 nginx certbot python3-certbot-nginx ufw unattended-upgrades

    Enable automatic security updates:

    shell
    sudo dpkg-reconfigure -plow unattended-upgrades

    Configure the firewall:

    shell
    sudo ufw allow OpenSSH
    sudo ufw allow 'Nginx Full'
    sudo ufw enable
    sudo ufw status

    On 1 GB plans, add swap so conversions do not trigger the OOM killer:

    shell
    sudo fallocate -l 2G /swapfile
    sudo chmod 600 /swapfile
    sudo mkswap /swapfile
    sudo swapon /swapfile
    echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

    Step 2: Install Calibre Command-Line Tools

    Calibre-Web needs calibredb to create a new library and ebook-convert for format conversion. The Ubuntu package is the simplest option and receives updates through apt:

    shell
    sudo apt -y install --no-install-recommends calibre
    ebook-convert --version

    --no-install-recommends keeps the install lean, but it still pulls in a sizable set of Qt libraries. That is expected.

    Want the newest Calibre? The official installer from calibre-ebook.com ships Calibre 9.x into /opt/calibre. Calibre-Web has supported Calibre 9 since 0.6.26. If you use it, install libegl1 libopengl0 libxcb-cursor0 first and point Calibre-Web at /opt/calibre/ebook-convert in Step 7.

    Step 3: Create the Service Account and Directories

    shell
    sudo useradd --system --home-dir /var/lib/calibre-web --create-home \
      --shell /usr/sbin/nologin calibreweb
    
    sudo mkdir -p /opt/calibre-web /srv/calibre/library
    sudo chown calibreweb:calibreweb /opt/calibre-web /srv/calibre/library
    sudo chmod 750 /var/lib/calibre-web /srv/calibre/library
    PathPurpose
    /opt/calibre-web/venvPython virtualenv with Calibre-Web and dependencies
    /var/lib/calibre-webSettings database (app.db), log, and service home directory
    /srv/calibre/libraryCalibre library (metadata.db plus book folders)

    Step 4: Install Calibre-Web

    Create the virtualenv and install the package from PyPI with the optional feature sets most self-hosters want:

    shell
    sudo -u calibreweb python3 -m venv /opt/calibre-web/venv
    sudo -u calibreweb /opt/calibre-web/venv/bin/pip install --upgrade pip wheel
    sudo -u calibreweb /opt/calibre-web/venv/bin/pip install "calibreweb[metadata,kobo,comics]==0.6.27"
    ExtraAdds
    metadataOnline metadata providers for fetching covers and descriptions
    koboKobo device sync
    comicsCBZ/CBR cover extraction and comic reader support

    Other extras exist (ldap, oauth, gdrive, gmail, goodreads). Add them only if you will use them, since each brings more dependencies.

    0.6.27 replaced the bleach sanitizer with nh3. A fresh pip install handles that automatically. If you are upgrading an older venv, confirm nh3 is present:

    shell
    sudo -u calibreweb /opt/calibre-web/venv/bin/pip show nh3

    Step 5: Create or Import the Library

    Option A: Start with an empty library

    shell
    sudo -u calibreweb calibredb list --with-library /srv/calibre/library
    ls -l /srv/calibre/library/metadata.db

    Running any calibredb command against an empty directory creates a fresh metadata.db.

    Option B: Import an existing library

    From your workstation, copy the entire Calibre library folder (the one containing metadata.db):

    shell
    rsync -avh --progress ~/Calibre\ Library/ user@books.example.com:/tmp/library/

    Then on the VPS:

    shell
    sudo rsync -a /tmp/library/ /srv/calibre/library/
    sudo chown -R calibreweb:calibreweb /srv/calibre/library
    sudo rm -rf /tmp/library

    Do not let desktop Calibre and Calibre-Web write to the same metadata.db at the same time (for example over a network share or sync tool). SQLite locking over those paths is unreliable and can corrupt the database. Pick one writer, or sync one direction only.

    Step 6: Set the Admin Password Before First Launch

    Calibre-Web ships with a default admin / admin123 login. Initialize the settings database and replace that password from the CLI so the default credentials are never reachable over the network:

    shell
    sudo -u calibreweb env CALIBRE_DBPATH=/var/lib/calibre-web \
      /opt/calibre-web/venv/bin/cps -s 'admin:REPLACE_WITH_A_STRONG_PASSWORD'

    Clear it from your shell history afterward:

    shell
    history -d $(history 1 | awk '{print $1}')

    Step 7: Create the systemd Service

    shell
    sudo pico /etc/systemd/system/calibre-web.service
    shell
    [Unit]
    Description=Calibre-Web ebook server
    After=network-online.target
    Wants=network-online.target
    
    [Service]
    Type=simple
    User=calibreweb
    Group=calibreweb
    Environment=CALIBRE_DBPATH=/var/lib/calibre-web
    Environment=HOME=/var/lib/calibre-web
    WorkingDirectory=/var/lib/calibre-web
    ExecStart=/opt/calibre-web/venv/bin/cps -i 127.0.0.1
    Restart=on-failure
    RestartSec=5
    
    # Hardening
    NoNewPrivileges=true
    PrivateTmp=true
    PrivateDevices=true
    ProtectSystem=strict
    ProtectHome=true
    ProtectKernelTunables=true
    ProtectKernelModules=true
    ProtectControlGroups=true
    RestrictSUIDSGID=true
    LockPersonality=true
    ReadWritePaths=/var/lib/calibre-web /srv/calibre
    
    [Install]
    WantedBy=multi-user.target

    -i 127.0.0.1 binds Calibre-Web to loopback so it is only reachable through Nginx. HOME points Calibre's own config directory at a writable path inside the sandbox, which ebook-convert needs.

    Start it:

    shell
    sudo systemctl daemon-reload
    sudo systemctl enable --now calibre-web
    sudo systemctl status calibre-web --no-pager
    curl -sI http://127.0.0.1:8083/login | head -n1

    You should see HTTP/1.1 200 OK.

    Step 8: Configure Nginx and TLS

    Define a login rate limit in the http context:

    shell
    sudo pico /etc/nginx/conf.d/ratelimit-calibreweb.conf
    shell
    limit_req_zone $binary_remote_addr zone=cw_login:10m rate=10r/m;

    Create the site:

    shell
    sudo pico /etc/nginx/sites-available/calibre-web
    shell
    server {
        listen 80;
        listen [::]:80;
        server_name books.example.com;
    
        # Ebook uploads can be large (PDFs, comics)
        client_max_body_size 500M;
    
        # Kobo sync sends large headers and responses
        proxy_buffer_size 128k;
        proxy_buffers 4 256k;
        proxy_busy_buffers_size 256k;
    
        location /login {
            limit_req zone=cw_login burst=5 nodelay;
            proxy_pass http://127.0.0.1:8083;
            include /etc/nginx/snippets/calibre-web-proxy.conf;
        }
    
        location / {
            proxy_pass http://127.0.0.1:8083;
            include /etc/nginx/snippets/calibre-web-proxy.conf;
        }
    }

    Shared proxy headers:

    shell
    sudo pico /etc/nginx/snippets/calibre-web-proxy.conf
    shell
    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-Scheme $scheme;
    proxy_read_timeout 300s;

    The long read timeout keeps conversions and large Kobo syncs from being cut off.

    Enable the site and request a certificate:

    shell
    sudo ln -s /etc/nginx/sites-available/calibre-web /etc/nginx/sites-enabled/
    sudo rm -f /etc/nginx/sites-enabled/default
    sudo nginx -t && sudo systemctl reload nginx
    sudo certbot --nginx -d books.example.com --redirect -m you@example.com --agree-tos --no-eff-email

    Certbot rewrites the server block for HTTPS and installs a renewal timer. Confirm it:

    shell
    sudo systemctl list-timers | grep certbot
    sudo certbot renew --dry-run

    Step 9: First-Run Configuration

    Browse to https://books.example.com and log in as admin with the password from Step 6.

    1. Database configuration: set the Calibre library location to /srv/calibre/library and save.
    2. Admin > Edit Basic Configuration > Feature Configuration:
      • Enable Uploads if you want to add books through the browser.
      • Restrict Allowed Upload Fileformats to what you actually use (for example epub,pdf,mobi,azw3,cbz).
    3. External binaries:
      • Path to converter: /usr/bin/ebook-convert (or /opt/calibre/ebook-convert if you used the official installer)
      • Leave the unrar path empty unless you need CBR support. If you do, sudo apt install unrar and set /usr/bin/unrar.
    4. Security settings: enable Limit failed login attempts and set a session protection level of Strong.
    5. Admin > Edit User admin: change the username from admin to something less guessable.

    Optional: Kobo Sync

    1. Admin > Edit Basic Configuration > Feature Configuration: enable Kobo sync and Proxy unknown requests to Kobo Store.
    2. Set Server External Port to 443.
    3. As each user, open Profile, generate a Kobo sync token, and copy the API endpoint into the api_endpoint= line of .kobo/Kobo/Kobo eReader.conf on the device.

    0.6.27 rewrites the library_sync URL in the Kobo init response, so sync now works correctly behind a reverse proxy without extra Nginx rewrites.

    Optional: kepubify

    Kobo devices render .kepub files better than plain EPUB. Install kepubify and point Calibre-Web at it:

    shell
    KV=$(curl -s https://api.github.com/repos/pgaskin/kepubify/releases/latest | grep -oP '"tag_name": "\K[^"]+')
    sudo curl -L -o /usr/local/bin/kepubify \
      "https://github.com/pgaskin/kepubify/releases/download/${KV}/kepubify-linux-64bit"
    sudo chmod 755 /usr/local/bin/kepubify
    kepubify --version

    Set the kepubify path to /usr/local/bin/kepubify under External binaries.

    OPDS for Reader Apps

    Calibre-Web exposes an OPDS catalog at:

    shell
    https://books.example.com/opds

    Add that URL in KOReader, Readest, Moon+ Reader, or any OPDS client, using a regular Calibre-Web account. Create a dedicated low-privilege user for each device rather than sharing the admin login.

    Step 10: Backups

    Two things matter: the Calibre library (metadata.db plus book files) and Calibre-Web's app.db (users, shelves, Kobo tokens, settings). Use SQLite's online backup so you never copy a database mid-write.

    shell
    sudo pico /usr/local/bin/calibre-web-backup
    shell
    #!/usr/bin/env bash
    set -euo pipefail
    
    DEST=/var/backups/calibre-web
    STAMP=$(date +%F)
    KEEP_DAYS=14
    
    mkdir -p "$DEST/$STAMP"
    
    sqlite3 /srv/calibre/library/metadata.db ".backup '$DEST/$STAMP/metadata.db'"
    sqlite3 /var/lib/calibre-web/app.db ".backup '$DEST/$STAMP/app.db'"
    
    tar --exclude='metadata.db' -czf "$DEST/$STAMP/library-files.tar.gz" -C /srv/calibre library
    
    find "$DEST" -mindepth 1 -maxdepth 1 -type d -mtime +$KEEP_DAYS -exec rm -rf {} +
    shell
    sudo chmod 750 /usr/local/bin/calibre-web-backup
    echo '30 3 * * * root /usr/local/bin/calibre-web-backup' | sudo tee /etc/cron.d/calibre-web-backup
    sudo /usr/local/bin/calibre-web-backup
    ls -lh /var/backups/calibre-web/$(date +%F)

    Ship /var/backups/calibre-web off the server with restic, rclone, or rsync to a second RamNode VPS or object storage. A backup on the same disk is not a backup.

    Updating Calibre-Web

    Read the release notes first, since some releases add required dependencies (0.6.27 required nh3).

    shell
    sudo /usr/local/bin/calibre-web-backup
    sudo systemctl stop calibre-web
    sudo -u calibreweb /opt/calibre-web/venv/bin/pip install --upgrade "calibreweb[metadata,kobo,comics]==NEW_VERSION"
    sudo systemctl start calibre-web
    sudo journalctl -u calibre-web -n 50 --no-pager

    Disable the in-app updater (Admin > Edit Basic Configuration > Feature Configuration) so updates only happen through pip, where you control the version.

    Troubleshooting

    SymptomCause and fix
    DB location is not validPath must be the folder containing metadata.db, and calibreweb must own it: sudo chown -R calibreweb:calibreweb /srv/calibre/library
    502 Bad GatewayService is down. Check sudo journalctl -u calibre-web -n 100 --no-pager
    Uploads fail with 413 Request Entity Too LargeRaise client_max_body_size in the Nginx site and reload
    Conversion hangs or failsVerify the converter path; check free -h for memory pressure; confirm HOME is set in the unit so Calibre can write its config
    Downloads failing with metadata conversion enabledKnown issue when the calibre binary path is invalid. Correct the path under External binaries
    Kobo sync errors or partial syncsConfirm the Nginx proxy buffer settings, external port 443, and that the device token belongs to the right user
    Kobo sync fails with AttributeError in rate limiterKnown 0.6.27 bug fixed in nightly; update when the next release ships
    Forgot admin passwordsudo systemctl stop calibre-web then rerun the cps -s 'user:newpass' command from Step 6 with CALIBRE_DBPATH set, then start the service
    /author pages very slow on huge librariesKnown performance issue with 150k+ titles. Prefer search and shelves for very large catalogs

    Logs live in two places:

    shell
    sudo journalctl -u calibre-web -f
    sudo tail -f /var/lib/calibre-web/calibre-web.log

    Security Checklist

    • Default admin123 password replaced before the service ever listened on the network
    • Admin account renamed
    • Service bound to 127.0.0.1, reachable only through Nginx over HTTPS
    • Login rate limited at Nginx and failed-login limiting enabled in-app
    • Uploads restricted to required formats; unrar left unset unless needed
    • Regular users have no admin, upload, or delete rights unless explicitly granted
    • Off-server backups tested with a restore at least once

    Next Steps

    • Pair Calibre-Web with a document workflow: drop new files into a watched folder and add them with calibredb add --with-library /srv/calibre/library from a cron job or systemd path unit.
    • Put the instance behind your SSO with the oauth or ldap extras, or use the new reverse proxy header login added in 0.6.27 with its shared secret header.
    • If you also self-host feeds, see the Miniflux and FreshRSS guides to round out a personal reading stack.