Skip to content

πŸ“™ Installing Calibre

calibre_banner

Calibre-Web is a self-hosted web application that provides a clean, browser-based interface to browse, read, download, and manage your existing Calibre eBook library.

Features:

  • πŸ—‚οΈ It connects to an existing Calibre database (your metadata.db)
  • πŸ” Lets you browse, search, read, and download eBooks via a browser
  • πŸ‘₯ Includes user management, OPDS feed for e-readers, metadata editing

Info

The project is open-source and can be downloaded here: https://github.com/janeczku/calibre-web.


πŸ“₯ Installation

This section describes how to install and run Podman services using systemd Quadlet, enabling containers to restart automatically. It allows the service to run in its own isolated rootless environment with a dedicated Linux user.

πŸ“‹ Requirements

Info

Calibre requires the installation of


βš™οΈ Configuration

πŸ“‹ Service Setup

Define Service Variables

# define the service name
SERVICE_NAME=calibre
Initialize Service Environment
# built-in bash safety and SERVICE_NAME validation
set -euo pipefail; [ -n "${SERVICE_NAME:-}" ] || { echo "SERVICE_NAME is empty"; exit 1; }

# automatically generate variables
SERVICE_HOME="/media/ssd/podman-users/${SERVICE_NAME}"
SERVICE_DIR="/media/ssd/podman/${SERVICE_NAME}"
SERVICE_USER="${SERVICE_NAME}_svc"
SERVICE_ADMIN="${SERVICE_NAME}_admins"

πŸ” Service Isolation

Configure Rootless Podman UID/GID Namespace Mappings
# allocate rootless Podman UID/GID mappings if missing
SUBID_SIZE=65536
allocate_subids_if_missing() {
  local user="$1"
  local start
  if ! grep -q "^${user}:" /etc/subuid; then
    start="$(awk -F: 'BEGIN { max = 100000 } { end = $2 + $3; if (end > max) max = end } END { print max }' /etc/subuid)"
    sudo usermod --add-subuids "${start}-$((start + SUBID_SIZE - 1))" "$user"
  fi
  if ! grep -q "^${user}:" /etc/subgid; then
    start="$(awk -F: 'BEGIN { max = 100000 } { end = $2 + $3; if (end > max) max = end } END { print max }' /etc/subgid)"
    sudo usermod --add-subgids "${start}-$((start + SUBID_SIZE - 1))" "$user"
  fi
}
allocate_subids_if_missing "${SERVICE_USER}"

# verify rootless Podman UID/GID namespace mappings exist
grep -q "^${SERVICE_USER}:" /etc/subuid || { echo "ERROR: missing subuid mappings for ${SERVICE_USER}"; exit 1; }
grep -q "^${SERVICE_USER}:" /etc/subgid || { echo "ERROR: missing subgid mappings for ${SERVICE_USER}"; exit 1; }

# validate that all subordinate ID ranges in a file are non-overlapping
validate_subid_ranges() {
  local file="$1"
  awk -F: '
    {
      start = $2
      end   = $2 + $3 - 1
      for (i = 1; i <= count; i++) {
        if (start <= ends[i] && end >= starts[i]) {
          printf "ERROR: overlapping ranges in %s\n", FILENAME
          printf "  %s:%s:%s overlaps %s:%s:%s\n",
            $1, $2, $3,
            names[i], starts[i], sizes[i]
          exit 1
        }
      }
      count++
      names[count]  = $1
      starts[count] = start
      ends[count]   = end
      sizes[count]  = $3
    }
  ' "$file"
}
validate_subid_ranges /etc/subuid
validate_subid_ranges /etc/subgid

# configure the rootless storage driver BEFORE Podman first initializes its storage.
# fuse-overlayfs is a reliable rootless overlay backend and avoids kernel-version
# quirks of native rootless overlay on ARM/Armbian. It is optional with the default
# userns used here, but recommended; set it before the first storage init so no
# 'podman system reset' is needed later.
sudo -u "${SERVICE_USER}" mkdir -p "${SERVICE_HOME}/.config/containers"
sudo -u "${SERVICE_USER}" tee "${SERVICE_HOME}/.config/containers/storage.conf" >/dev/null <<'EOF'
[storage]
driver = "overlay"

[storage.options.overlay]
mount_program = "/usr/bin/fuse-overlayfs"
EOF

# rebuild rootless Podman state to ensure existing containers/storage use current namespace mappings
# use a login shell (-i) so HOME points at the service user's home
# (storage now initializes with fuse-overlayfs; no 'podman system reset' needed)
sudo -iu "${SERVICE_USER}" podman system migrate
Configure Secure Service Directory Permissions
# Apply the admin/service ACL policy to one directory tree, deterministically.
#   $1 = tree to treat
#   $2 = (optional) a sub-path to skip entirely (e.g. a container data subtree)
#
# Why setfacl, not chmod: on an ACL'd tree, chmod edits only the mask, never group::,
# so stale execute bits survive. setfacl sets every entry explicitly (mask included),
# lowercase β€” the result is reproducible and no execute bit can resurface.
apply_acl_tree() {
    local tree="$1"
    local skip="${2:-}"
    local prune=()
    [ -n "$skip" ] && prune=( -path "$skip" -prune -o )                                         # skip this subtree if a 2nd arg is given

    sudo find "$tree" "${prune[@]}" -exec chown "${SERVICE_USER}:${SERVICE_ADMIN}" {} +         # service user owns; admin group is the owning group

    # directories β€” access ACL (perms on the existing dirs)
    sudo find "$tree" "${prune[@]}" -type d -exec setfacl    -m g:${SERVICE_ADMIN}:rwx {} +     # admin group: access existing dirs
    sudo find "$tree" "${prune[@]}" -type d -exec setfacl    -m u:${SERVICE_USER}:rwx {} +      # service user: access existing dirs
    sudo find "$tree" "${prune[@]}" -type d -exec setfacl    -m u::rwx,g::rwx,o::-,m::rwx {} +  # owner/group rwx, deny others, pin mask rwx

    # directories β€” default ACL (inherited by NEW files/dirs created later)
    sudo find "$tree" "${prune[@]}" -type d -exec setfacl -d -m g:${SERVICE_ADMIN}:rwx {} +     # admin group: inherit access on new items
    sudo find "$tree" "${prune[@]}" -type d -exec setfacl -d -m u:${SERVICE_USER}:rwx {} +      # service user: inherit access on new items
    sudo find "$tree" "${prune[@]}" -type d -exec setfacl -d -m u::rwx,g::rwx,o::- {} +         # default owner/group/other for new items

    # files β€” access ACL (rw only, never executable)
    sudo find "$tree" "${prune[@]}" -type f -exec setfacl    -m g:${SERVICE_ADMIN}:rw {} +      # admin group: access existing files
    sudo find "$tree" "${prune[@]}" -type f -exec setfacl    -m u:${SERVICE_USER}:rw {} +       # service user: access existing files
    sudo find "$tree" "${prune[@]}" -type f -exec setfacl    -m u::rw,g::rw,o::-,m::rw {} +     # owner/group rw, deny others, pin mask rw

    sudo find "$tree" "${prune[@]}" -type d -exec chmod g+s {} +                                # setgid: new items inherit the group (a MODE bit; ACLs can't set it)
}

# --- SERVICE_HOME: Podman's private runtime home ------------------------------
# Own the home + set setgid, then ACL ONLY the .config subtree (containers.conf,
# storage.conf, and the *.network / *.container quadlets you hand-edit) so admins
# can edit them sudo-less.
#
# Leave ~/.local (storage) and ~/.cache to Podman: ACLs on the overlay store break
# the runtime ("OCI permission denied" on the `merged` mount). Admin reaches them via sudo.
sudo chown "${SERVICE_USER}:${SERVICE_ADMIN}" "${SERVICE_HOME}"                       # service user owns; admin group is the owning group
sudo chmod u=rwx,g=rwx,o=,g+s "${SERVICE_HOME}"                                       # owner/admin access; deny others; inherit group on new items
apply_acl_tree "${SERVICE_HOME}/.config"                                              # .config subtree: full sudo-less admin ACL treatment

# --- bind-mount data dirs: created + owned BEFORE the SERVICE_DIR ACL pass ---------
# Each container writes as a mapped subuid, so its data dir must be owned by that subuid
# (chown as root; only root crosses the id range). We create + own them FIRST so they
# neither inherit SERVICE_DIR's default ACL (inheritance happens at creation) nor get
# walked by apply_acl_tree (which prunes data/).
#
# No ACLs here: Postgres refuses a group-accessible PGDATA. Admin reaches them via sudo;
# back Postgres up with pg_dumpall, not a raw file copy.
SUBUID_BASE="$(awk -F: -v u="${SERVICE_USER}" '$1==u {print $2}' /etc/subuid)"
SUBGID_BASE="$(awk -F: -v u="${SERVICE_USER}" '$1==u {print $2}' /etc/subgid)"

# data subdir : in-container uid/gid it must be owned by
data_dirs=(
    "calibre:1000"        # /config β€” calibre-web's app user (PUID 1000)
    "library:1000"        # /books  β€” same app user (PUID 1000)
)

mkdir -p "${SERVICE_DIR}/data"                                                        # the data/ parent: plain, no ACL
sudo chown "${SERVICE_USER}:${SERVICE_ADMIN}" "${SERVICE_DIR}/data"                   # owner/admin
sudo chmod 0750 "${SERVICE_DIR}/data"                                                 # children are set individually below
for entry in "${data_dirs[@]}"; do
    sub="${entry%%:*}"                                  # subdir name
    ids="${entry#*:}"                                   # "uid" or "uid:gid"
    uid="${ids%%:*}"                                    # uid
    gid="${ids#*:}"; [ "$gid" = "$ids" ] && gid="$uid"  # gid (defaults to uid)
    dir="${SERVICE_DIR}/data/${sub}"
    sudo mkdir -p "$dir"
    sudo chown -R "$((SUBUID_BASE + uid - 1)):$((SUBGID_BASE + gid - 1))" "$dir"   # map both to host subids
    sudo chmod 0700 "$dir"
done

# --- SERVICE_DIR: application data, env files, configuration -------------------
# Full treatment so the admin group gets sudo-less access AND the service user keeps
# access even on admin-created files. data/ is EXCLUDED: those dirs are owned by the
# containers' subuids and need engine-specific modes (Postgres rejects a group/other-
# accessible PGDATA), so they are handled separately just below.
apply_acl_tree "${SERVICE_DIR}" "${SERVICE_DIR}/data"                                 # treat everything EXCEPT data/
Configure Secure NGINX Static Assets Permissions
# Grant the NGINX worker user (www-data) read-only access to assets subtrees.
#   $1     = service directory β†’ traverse-only (x)
#   $2..$n = assets subtrees served directly by NGINX (configs, icons, favicon, manifest, ...)
apply_acl_nginx() {
    local tree="$1"; shift
    local assets

    sudo setfacl -m u:www-data:x "$tree"                                        # traverse only: pass through, no listing, no reading

    for assets in "$@"; do
        # assets directories β€” enter + list, existing and future
        sudo find "$assets" -type d -exec setfacl    -m u:www-data:rx {} +      # www-data: read existing dirs
        sudo find "$assets" -type d -exec setfacl -d -m u:www-data:rx {} +      # www-data: inherit read on new items

        # assets files β€” www-data read-only, owner/admin rw, never executable.
        # Entries AND mask are pinned explicitly: a bare `setfacl -m u:www-data:r`
        # would recalculate the mask to the union of all entries, resurfacing the
        # latent rwx inherited from the default ACL (files would show group rwx).
        sudo find "$assets" -type f -exec setfacl \
          -m u:www-data:r,u:${SERVICE_USER}:rw,g:${SERVICE_ADMIN}:rw,u::rw,g::rw,o::-,m::rw {} +
    done
}

# --- SERVICE_DIR/nginx + SERVICE_DIR/icons: served directly by NGINX -----------
# Created AFTER the apply_acl_tree pass so they inherit the default ACL + setgid
# group (service user/admin keep full access); www-data is then layered on top
# as a read-only named entry.
sudo mkdir -p "${SERVICE_DIR}/nginx" "${SERVICE_DIR}/icons"
sudo chown "${SERVICE_USER}:${SERVICE_ADMIN}" "${SERVICE_DIR}/nginx" "${SERVICE_DIR}/icons"
apply_acl_nginx "${SERVICE_DIR}" "${SERVICE_DIR}/nginx" "${SERVICE_DIR}/icons"

πŸ›‘οΈ Rootless Podman Defaults

Configure rootless Podman per-user configuration directory
# configure rootless Podman defaults for this service user
# - store per-user Podman configuration under ~/.config/containers
sudo -u "${SERVICE_USER}" mkdir -p "${SERVICE_HOME}/.config/containers/systemd"
Configure rootless Podman defaults
# configure rootless Podman defaults for this service user
# - use k8s-file logging for easier log rotation and inspection
sudo -u "${SERVICE_USER}" tee "${SERVICE_HOME}/.config/containers/containers.conf" >/dev/null <<EOF
[containers]
log_driver = "k8s-file"

[engine]
healthcheck_events=false
EOF

πŸ”§ Service Configuration

Setup calibre Parameters

Before deploying, you need to define a few environment variables that will be used throughout the setup process.

  • HOST_PORT: external port used by NGINX to route traffic to the service
###################################################################################
# NGINX Proxy Configuration
###################################################################################
HOST_PORT=10025
Create Environment files
sudo -u "${SERVICE_USER}" tee "${SERVICE_DIR}/calibre.env" >/dev/null <<EOF
###################################################################################
# Use container UID/GID 1000:1000
###################################################################################
PUID=1000
PGID=1000

###################################################################################
# NGINX Proxy Configuration
###################################################################################
HOST_PORT=${HOST_PORT}

###################################################################################
# Calibre Configuration
###################################################################################
CALIBRE_VERSION=latest
TZ=Etc/UTC
EOF

Keep the .env files

All the secret informations will be stored in the .env files.

βš™οΈ Patching Calibre

Initialize and patch Calibre
# copy to library Calibre database, download a sample from
wget -P data/library/ https://github.com/janeczku/calibre-web/raw/master/library/metadata.db
# setup of Dockerfile
tee build/Dockerfile > /dev/null <<'EOF'
#############################################
##### Build stage: apply Calibre-Web customization
#############################################
ARG CALIBRE_VERSION=latest
FROM lscr.io/linuxserver/calibre-web:${CALIBRE_VERSION} AS build

# Reverse order for downloaded file names:
# From: "{title} - {author}"
#   To: "{author} - {title}"
#
# This patches the helper responsible for building the download file name
RUN set -eux; \
    sed -i -E \
      "s/^([[:space:]]*)file_name = file_name \+ ' - ' \+ book\.authors\[0\]\.name/\
\1file_name = book.authors[0].name + ' - ' + file_name/" \
      /app/calibre-web/cps/helper.py

# Update apple-touch-icon + add high-res PNG icons for Android + Manifest
RUN set -eux; \
    TPL_DIR="/app/calibre-web/cps/templates"; \
    OLD='<link rel="apple-touch-icon" sizes="140x140" href="{{ url_for('"'"'static'"'"', filename='"'"'favicon.ico'"'"') }}">'; \
    NEW='<link rel="apple-touch-icon" sizes="180x180" href="{{ url_for('"'"'static'"'"', filename='"'"'apple-touch-icon.png'"'"') }}">\
<link rel="icon" type="image/png" sizes="192x192" href="{{ url_for('"'"'static'"'"', filename='"'"'android-chrome-192x192.png'"'"') }}">\
<link rel="manifest" href="{{ url_for('"'"'static'"'"', filename='"'"'app.webmanifest'"'"') }}">'; \
    sed -i "s|$OLD|$NEW|g" "$TPL_DIR"/*.html

#############################################
##### Final image: base image + patched helper
#############################################
ARG CALIBRE_VERSION
FROM lscr.io/linuxserver/calibre-web:${CALIBRE_VERSION}

# Copy the patched files from the build stage into the final image
COPY --from=build /app/calibre-web/cps/helper.py /app/calibre-web/cps/helper.py
COPY --from=build /app/calibre-web/cps/templates/*.html /app/calibre-web/cps/templates/
EOF

🧩 Quadlet Service

Create Network Podman Quadlet
# podman quadlet: create network
sudo -u "${SERVICE_USER}" tee "${SERVICE_HOME}/.config/containers/systemd/calibre.network" >/dev/null <<EOF
[Network]
NetworkName=calibre
EOF
Create Build Cache Podman Quadlet
# podman quadlet: create calibre build cache
sudo -u "${SERVICE_USER}" tee "${SERVICE_HOME}/.config/containers/systemd/calibre-builder.build" >/dev/null <<'EOF'
# builds & tags only the build stage
[Unit]
Description=Build calibre builder stage (cache image)

[Build]
Target=build
ImageTag=localhost/calibre-builder:cache
File=/media/ssd/podman/calibre/build/Dockerfile
SetWorkingDirectory=/media/ssd/podman/calibre/build
PodmanArgs=--build-arg CALIBRE_VERSION=${CALIBRE_VERSION}

[Service]
EnvironmentFile=/media/ssd/podman/calibre/calibre.env
EOF
Create Build Podman Quadlet
# podman quadlet: create calibre build
sudo -u "${SERVICE_USER}" tee "${SERVICE_HOME}/.config/containers/systemd/calibre.build" >/dev/null <<'EOF'
# depends on the calibre-builder
[Unit]
Description=Build calibre image
After=calibre-builder-build.service
Requires=calibre-builder-build.service

[Build]
ImageTag=localhost/calibre:${CALIBRE_VERSION}
File=/media/ssd/podman/calibre/build/Dockerfile
SetWorkingDirectory=/media/ssd/podman/calibre/build
PodmanArgs=--build-arg CALIBRE_VERSION=${CALIBRE_VERSION}

[Service]
EnvironmentFile=/media/ssd/podman/calibre/calibre.env
# After a successful (re)build, drop now-untagged old image versions.
# Dangling-only: tagged images (calibre:${CALIBRE_VERSION}, calibre-builder:cache) are kept.
ExecStartPost=-/usr/bin/podman image prune -f
EOF
Create Calibre Server Podman Quadlet
# podman quadlet: create calibre server
sudo -u "${SERVICE_USER}" tee "${SERVICE_HOME}/.config/containers/systemd/calibre-server.container" >/dev/null <<'EOF'
[Unit]
Description=calibre server
# give up after 6 failed starts within 30 min (podmanctl --restart clears it)
StartLimitIntervalSec=1800
StartLimitBurst=6

[Container]
EnvironmentFile=/media/ssd/podman/calibre/calibre.env
# Start the already-built image directly. Referencing the .build unit
# (Image=calibre.build) would force a podman build on every boot; deploys
# rebuild explicitly via podmanctl, so the container only needs the finished tag.
Image=localhost/calibre:${CALIBRE_VERSION}
ContainerName=calibre-server
Network=calibre.network

# No User= needed: the LinuxServer calibre-web image starts s6 as root and drops to
# PUID/PGID (both 1000, set in calibre.env). The data dir is pre-chowned to 1000's
# mapped subuid in the permissions step, so a plain :rw mount works.

# plain bind mount (data dir already owned by 1000's subuid via the permissions step)
Volume=/media/ssd/podman/calibre/data/calibre:/config:rw
Volume=/media/ssd/podman/calibre/data/library:/books:rw

PublishPort=127.0.0.1:${HOST_PORT}:8083

HealthCmd=curl -fsS -o /dev/null http://127.0.0.1:8083/login
HealthStartPeriod=180s
HealthInterval=60s
HealthTimeout=3s
HealthRetries=3
HealthOnFailure=kill

[Service]
EnvironmentFile=/media/ssd/podman/calibre/calibre.env
UMask=0007
SuccessExitStatus=143

Restart=on-failure
RestartSec=10
RestartSteps=5
RestartMaxDelaySec=300
TimeoutStartSec=600

[Install]
WantedBy=default.target
EOF

UMask=0007: removes all permissions for others while preserving read/write access for the service owner and admin group on newly created files and directories SuccessExitStatus=143: treat a SIGTERM-style exit (143) as a clean stop, not a failure Restart=on-failure + StartLimit*: retry a failed start with exponential backoff (10 s β†’ 5 min) and give up after 6 failed starts within 30 min β€” podmanctl --restart clears the limit


▢️ Start calibre

Enable and Start Quadlet Services
# open an interactive shell as the service user
sudo -iu "${SERVICE_USER}"

# reload systemd user units
systemctl --user daemon-reload

# build the calibre image (first install; podmanctl --update rebuilds afterwards)
systemctl --user start calibre-build.service

# start Podman Quadlet services
systemctl --user start calibre-server.service

# verify service status
systemctl --user status calibre-server.service

To follow the service logs in real time, run sudo journalctl _UID=$(id -u ${SERVICE_USER}) -f from the Debian account.


πŸš€ Deploy Calibre

Install NGINX

NGINX needs to be installed, follow the NGINX section.

Configure NGINX

NGINX needs to be configured using a file in /etc/nginx/sites-enabled directory.
This configuration file specify the documentation path:

# Per-client rate-limit bucket for the login endpoint. Must live in http{}
# context, so it sits outside the server{} block. $limit_key is the shared key
# from nginx.conf: the address for IPv4, the /64 prefix for IPv6 β€” keying on a
# full IPv6 address would let one subscriber prefix mint 2^64 empty buckets and
# bypass the limit entirely.
limit_req_zone $limit_key zone=calibre_auth:10m rate=10r/m;

# The app container, published on the loopback by its Quadlet unit. It sits
# behind an upstream block for one reason: `keepalive`. Without it nginx opens β€”
# and immediately closes β€” a brand-new TCP connection for EVERY proxied request,
# and on this host each of those crosses rootless Podman's userspace port
# forwarder. Calibre-Web serves its whole /static/ tree and every cover
# thumbnail through the app, so a shelf page is dozens of proxied requests,
# each of which paid its own handshake through that forwarder.
# `proxy_http_version 1.1` (already set in every location) and an empty
# `Connection` header are what let a connection go back into this pool instead
# of being torn down.
# The pool is deliberately small and short-lived: this upstream's own idle
# timeout is not something we control, and a connection nginx holds past it
# answers the request that races the close with a 502. Fifteen seconds still
# covers a whole page load, which is where the benefit sits.
upstream calibre_app {
  server 127.0.0.1:10025;
  keepalive 16;
  keepalive_requests 1000;
  keepalive_timeout 15s;
}

server {
  server_name calibre.domain.fr;

  # The security header quartet (HSTS, nosniff, X-Frame-Options, Referrer-Policy)
  # is inherited from the http{} block in nginx.conf β€” this file declares none.
  # X-Frame-Options stays SAMEORIGIN there rather than DENY precisely because
  # Calibre-Web's e-book reader frames its own pages.
  # WARNING: add_header does NOT accumulate. A single add_header added here, or
  # in any location{} below, replaces that whole inherited set for that scope β€”
  # silently, with no warning from `nginx -t`. If one is ever needed, restate all
  # four alongside it.

  # setup 404 error_page
  error_page 404 /404.html;
  include snippets/error-404.conf;

  # show maintenance page when backend is down
  error_page 502 503 504 /maintenance.html;
  include snippets/error-maintenance.conf;

  # Calibre-Web's login form posts here, so this is the route a password guesser
  # would hammer. Rate-limited before the app spends any work on it; rejections
  # give 429. Exact match, so it wins over `location /`.
  location = /login {
    limit_req zone=calibre_auth burst=5 nodelay;
    limit_req_status 429;

    proxy_pass http://calibre_app;
    proxy_http_version 1.1;
    include proxy_params;
    # Empty Connection header: keeps the pooled upstream connection alive.
    proxy_set_header Connection "";
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Port $server_port;
  }

  # reverse proxy
  location / {
    proxy_pass http://calibre_app;

    # keep it HTTP/1.1
    proxy_http_version 1.1;

    # forwarded headers. proxy_params APPENDS to the client's own
    # X-Forwarded-For, so a caller can prepend whatever hops it likes; NGINX is
    # the edge here and there is no upstream chain worth keeping, so overwrite
    # it with the real peer and the app can never be handed a forged hop.
    include proxy_params;
    # Empty Connection header: keeps the pooled upstream connection alive.
    proxy_set_header Connection "";
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Port $server_port;

    # application-specific tuning
    proxy_read_timeout 600s;
    proxy_send_timeout 600s;
    client_max_body_size 100M;

    # Stream the body straight to the app instead of spooling it to the proxy's
    # disk first. With request buffering on (the default), NGINX reads the WHOLE
    # body into client_body_temp before it opens the upstream connection β€” so an
    # unauthenticated POST to any path here could write 100 MB to the SSD long
    # before Calibre-Web ever got the chance to refuse it.
    proxy_request_buffering off;
  }
}
# restart nginx
sudo nginx -t && sudo service nginx restart

Replace calibre.domain.fr by the name of your website.

Activate HTTPS

To activate HTTPS protocol, follow theΒ Let's Encrypt section.


βš™οΈ Configure Calibre

πŸ›‘οΈ Update Calibre-Web admin account

For security reasons, you must change the default administrator password immediately.
Log in using the factory credentials, then update the password in the admin settings:

  • Username: admin
  • Password: admin123

βš™οΈ Configure Calibre-Web

Configure Calibre database location

  1. Go to: Settings β†’ Edit Calibre Database Configuration
  2. Set Location of Calibre database to the folder containing Calibre library (metadata.db, ex: /books)
  3. Click Save

Enable uploads for admin

  1. Go to: Admin β†’ Edit Basic Configuration
  2. Under Feature Configuration, enable Allow Uploads
  3. Click Save

βš™οΈ Configure Calibre-Desktop

Use the title for sorting

  1. Open Preferences β†’ Ajustements
  2. Find Contrôler le tri des titres et séries dans l'affichage de la bibliothèque
  3. Set:
    title_series_sorting = 'strictly_alphabetic'
    
  4. Click Appliquer les changements Γ  cet ajustement
  5. Restart Calibre

Update the book filename

  1. Open Preferences β†’ Saving books to disk
  2. Uncheck Save cover separately
  3. Uncheck Save metadata in separate OPF file
  4. Save template: {authors} - {title}