- image: crocodilestick/calibre-web-automated:latest
+ image: ghcr.io/new-usemame/calibre-web-nextgen:latestdocker compose pull && docker compose up -dLibrary, settings, users, OAuth tokens, and KOReader sync state are preserved. Switching back is the reverse one-line change.
Not using a terminal? If you run Docker through a NAS or a GUI, follow a step-by-step guide instead — they cover both a fresh install and switching from CWA, with the exact buttons for your platform: Synology · Unraid · Portainer · TrueNAS SCALE · all guides. Configuration not matching? Open an issue or ask on Discord and we'll walk you through it.
- Bug? File it here.
- Feature idea? Open a request. Anything goes, no checklist required — even half-formed ideas are welcome and help prioritize what to look at next.
- New here? See Quick start below.
- Want to back the work? Sponsor on GitHub — no rewards, no paywalled features, one-time or monthly. Here's what it actually pays for.
- Setting up with an AI assistant (Claude, ChatGPT, etc.)? Point it at
AI_README.md— a setup guide written for the assistant to follow, verify, and hand back to you working. - Wondering how AI is used here? How AI is used — used heavily to develop this fork, not at all in the software you run, and what gates it.
Prefer a browsable version? The same guides live in the project wiki — install, configuration, KOReader/Kobo sync, and troubleshooting, split into pages with a sidebar.
- Why this fork exists
- What's included
- Quick start
- Full Docker Compose setup
- Runtime path overrides for packagers
- First run
- Migrating
- Pair with Shelfmark
- Common configurations
- Troubleshooting
- Differences from upstream
- Contributing
- Supporting the project
- How AI is used
- Credits
CWA has an open PR queue with community-submitted bug fixes that aren't in the latest published image. This build picks the safe ones, ships them in regular releases, and adds fresh fixes for high-impact bugs that don't have an upstream PR yet. Feature work happens here too, driven by what users ask for in the issue tracker.
The data format and configuration are byte-compatible with upstream, so swapping images is reversible and migrations aren't needed in either direction.
Everything CWA has, plus the patches in CHANGES-vs-upstream.md. A representative slice of fixes that are in this build but not in crocodilestick/calibre-web-automated:latest:
- Cover saves from Hardcover, Google Books, iTunes, and Open Library (was returning "not a valid image" since 4.0.6).
- Metadata search and the book-delete button on Safari.
- Generate Kobo Auth Token (was returning a blank page).
- Kobo bookmark sync no longer crashes when the client omits
Location. - Auth check added to 14 admin routes (
cwa_logs,convert,epub_fixer, and others) that previously didn't require admin. - Cover-enforcer shell-injection on filenames containing quotes.
- Reverse proxy: user-profile saves honor the path prefix.
- Docker healthcheck follows the
/ → /login302 instead of failing on it. .cbrand.cbzuse IANA-registered mimetypes in OPDS feeds.- Higher-resolution covers from Google Books, Amazon, and an iTunes-backed fallback for high-DPI e-readers (Libra Color, etc.).
- Translation PRs merged: ja, fr, cs, hu, zh_Hans, zh_Hant, and others.
Requirements: Docker and Docker Compose.
-
Make a folder for your library:
mkdir -p ~/calibre-web/{config,library,ingest} cd ~/calibre-web
-
Save this as
docker-compose.yml:services: calibre-web: image: ghcr.io/new-usemame/calibre-web-nextgen:latest container_name: calibre-web environment: - PUID=1000 - PGID=1000 - TZ=America/New_York # change to your timezone volumes: - ./config:/config # settings, user db, logs - ./library:/calibre-library # books live here - ./ingest:/cwa-book-ingest # drop new books here to import ports: - 8083:8083 restart: unless-stopped
-
Start it:
docker compose up -d
-
Open
http://localhost:8083, log in withadmin/admin123, change the password.
Drop an .epub into ./ingest/ and it will appear in your library within a few seconds.
Files in your library and ingest folders should be owned by your
PUID:PGIDuser (1000 by default), not root. If you've copied books in as root, run once:sudo chown -R 1000:1000 ~/calibre-web.
A more complete compose file, with each option documented:
services:
calibre-web:
image: ghcr.io/new-usemame/calibre-web-nextgen:latest
container_name: calibre-web
environment:
# Match your host user/group so files in your library
# are writable from both the container and the host.
- PUID=1000
- PGID=1000
# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
- TZ=America/New_York
# Override the in-container port if you need to.
# If set below 1024, also uncomment cap_add below.
- CWA_PORT_OVERRIDE=8083
# Set this if your /config or /calibre-library volumes are
# on an NFS or SMB share. See "Network shares" below.
- NETWORK_SHARE_MODE=false
# If you sit behind multiple proxies (e.g. Cloudflare Tunnel
# then nginx then CWA), set this to the total proxy count so
# session protection sees the right client IP. Default 1.
- TRUSTED_PROXY_COUNT=1
# Optional: Hardcover API token for the Hardcover metadata
# provider. Free; sign up at https://hardcover.app/account/api
# - HARDCOVER_TOKEN=eyJhbGciOiJIUzI1NiI...
# Optional: your own ComicVine API key. ComicVine works without
# one, on a key shared by every install, so it can hit the rate
# limit. Free; sign up at https://comicvine.gamespot.com/api/
# - COMICVINE_API_KEY=...
# Optional: override paths inside the container. The matching
# volume targets below must use the same paths.
# - CWA_INGEST_FOLDER=/cwa-book-ingest
# - CWA_CALIBRE_LIBRARY_DIR=/calibre-library
# - CWA_TMP_CONVERSION_DIR=/config/.cwa_conversion_tmp
volumes:
# Settings, user database, logs. Empty folder for new installs;
# for existing CWA users, point at your existing /config.
- /path/to/config:/config
# Your Calibre library. New install? Use an empty folder and
# CWA will set one up. Existing user? Point at the folder
# containing your metadata.db.
- /path/to/library:/calibre-library
# Drop new books here to import them. WARNING: files in this
# folder are DELETED after processing. Don't point this at a
# folder you also use as long-term storage.
- /path/to/ingest:/cwa-book-ingest
# Optional: bind your existing Calibre plugins folder
# - /path/to/calibre-plugins:/config/.config/calibre/plugins
ports:
- 8083:8083
# Uncomment if CWA_PORT_OVERRIDE is below 1024.
# cap_add:
# - NET_BIND_SERVICE
restart: unless-stopped| Volume | What it is | Notes |
|---|---|---|
/config |
App settings, user accounts, OAuth tokens, KOReader sync state, logs | Empty folder for new installs. Carries over from CWA verbatim. |
/calibre-library |
Books and Calibre's metadata.db |
If empty, CWA creates a fresh library. If multiple metadata.db files exist inside, CWA picks the largest. |
/cwa-book-ingest |
Drop zone for new books | Files here are deleted after processing. Don't park books here long-term. |
Don't nest the binds. All three should be separate top-level folders. Putting
ingestinsidelibraryproduces recursive ingest behavior.
Bare-metal and distro packages can configure all three runtime paths from the
process environment instead of editing dirs.json inside the installation:
| Environment variable | dirs.json fallback |
Compiled-in default |
|---|---|---|
CWA_INGEST_FOLDER |
ingest_folder |
/cwa-book-ingest |
CWA_CALIBRE_LIBRARY_DIR |
calibre_library_dir |
/calibre-library |
CWA_TMP_CONVERSION_DIR |
tmp_conversion_dir |
/config/.cwa_conversion_tmp |
Each non-blank environment value wins for its key. If it is unset or blank,
CWNG reads that key from the file selected by CWA_DIRS_JSON; a missing or
malformed file, a non-object document, or a null/blank value falls back to the
compiled-in default. Existing hand-edited dirs.json files therefore remain
supported.
Runtime path values are trimmed and lexically normalized, and must be absolute,
non-root paths without a .. component. Repeated separators, . components,
and trailing separators are collapsed without resolving symlinks. A non-blank
environment or dirs.json value that violates that contract stops the affected
startup service instead of letting an unsafe path reach file watchers or
recursive ownership operations.
For example, a systemd unit can load a packager-owned file:
[Service]
EnvironmentFile=/etc/calibre-web-nextgen/paths.envCWA_INGEST_FOLDER=/srv/calibre-web-nextgen/ingest
CWA_CALIBRE_LIBRARY_DIR=/srv/calibre/library
CWA_TMP_CONVERSION_DIR=/var/cache/calibre-web-nextgen/conversionWhen CWA_CALIBRE_LIBRARY_DIR is set, it is authoritative. Automatic library
discovery will leave dirs.json unchanged; if discovery finds a different
library, startup stops and reports both paths so the environment file can be
corrected.
- Open the UI at
http://your-host:8083. - Log in with
admin/admin123. - Change the admin password (Profile → Account).
- Go to Admin → Edit Basic Configuration → Feature Configuration and enable Allow Uploads. Without this, the metadata-fetch and cover-from-URL features can't write to your library.
- Drop a book into your ingest folder. It should appear in the library within a few seconds.
The Admin → Settings panel has many optional toggles (auto-convert formats, automatic backups, EPUB fixer, KOReader sync, OAuth, etc.). The upstream wiki is the source of truth for those; this fork doesn't change them.
Calibre-Web NextGen ships new versions regularly — often weekly. Updating means pulling the new image and recreating the container; your library, settings and reading progress live in the mounted volumes, so they're left untouched.
Update once, by hand:
docker compose pull calibre-web && docker compose up -d calibre-web(Use your own service name if it isn't calibre-web.)
Update automatically with Watchtower (the maintained fork). Add it alongside CWA and label the CWA service so Watchtower only ever touches this one container — your other containers are left alone:
services:
calibre-web:
image: ghcr.io/new-usemame/calibre-web-nextgen:latest
labels: ["com.centurylinklabs.watchtower.enable=true"]
# ...rest of your config
watchtower:
image: nickfedor/watchtower
volumes: ["/var/run/docker.sock:/var/run/docker.sock"]
command: --label-enable --cleanup --interval 86400 # check daily, remove old images
restart: unless-stoppedThe in-app Admin → NextGen Settings → Automatic updates panel shows these same steps, and the "Update available" banner has an Update now button that gives the right command for your setup (Compose, docker run, Unraid, Portainer/Synology).
Calibre-Web NextGen is a standard OCI image, so it runs under Podman too — same image, no separate build:
podman run -d --name calibre-web \
-e PUID=1000 -e PGID=1000 -e TZ=America/New_York \
-p 8083:8083 \
-v /path/to/config:/config \
-v /path/to/library:/calibre-library \
-v /path/to/ingest:/cwa-book-ingest \
ghcr.io/new-usemame/calibre-web-nextgen:latestRootless Podman remaps user IDs, so if the container can't write to your volumes, add --userns=keep-id (or run it rootful). Podman also has native automatic updates (podman auto-update) with rollback — a step-by-step guide is coming once we've verified it against this image.
One line. Stop the container, swap the image, start it.
- image: crocodilestick/calibre-web-automated:latest
+ image: ghcr.io/new-usemame/calibre-web-nextgen:latestdocker compose pull && docker compose up -dSettings, users, OAuth tokens, and KOReader sync state are preserved. The data format is identical, so reverting is the reverse one-line change.
- Stop your existing Calibre-Web container.
- In the new compose file, point
/configat the same/configfolder you used for Calibre-Web. - Whatever you bound as
/booksin Calibre-Web should be bound as/calibre-libraryhere. - Pick an empty folder for
/cwa-book-ingest(it's CWA-specific; no equivalent in stock CW). - Start the container.
Users, settings, and shelves carry over. The first launch takes a few extra seconds while CWA registers itself with the existing app database.
Shelfmark by @calibrain is a self-hosted book search and request interface. Users search across torrent, usenet, IRC, and direct sources from a single UI; Shelfmark hands the download to your client of choice and drops the finished file straight into the CWA ingest folder, where this build picks it up automatically. Multi-user requests are built in, so you can share an instance with household readers and approve their picks.
Add it alongside calibre-web in the same compose file:
shelfmark:
image: ghcr.io/calibrain/shelfmark:latest
container_name: shelfmark
environment:
- PUID=1000
- PGID=1000
- TZ=America/New_York
- SEARCH_MODE=universal
# Point Shelfmark at CWA's app.db (read-only mount below) so users
# log in to Shelfmark with their existing CWA credentials.
- CWA_DB_PATH=/auth/cw-config/app.db
# Optional: shows a "Library" button in Shelfmark's header that
# links back to this CWA instance.
- CALIBRE_WEB_URL=http://your-host:8083
volumes:
- /path/to/shelfmark-config:/config
# Read-only mount of your CWA config dir for the auth integration.
- /path/to/cwa-config:/auth/cw-config:ro
# Shelfmark's destination folder = CWA's ingest folder.
# Downloads land here and this build ingests them on the next watch tick.
- /path/to/cwa-ingest:/books
# If you use a torrent or usenet client, mount its downloads dir
# at the same path you mounted in the client itself, so Shelfmark
# can locate the completed file.
- /path/to/downloads:/downloads
ports:
- 8084:8084
restart: unless-stoppedAfter Shelfmark starts, open it and pick Settings → Security → Authentication Method → Calibre-Web Database, then Sync from Calibre-Web to import users. The Shelfmark docs cover Prowlarr, qBittorrent, SABnzbd, and IRC source setup.
Shelfmark went into maintenance-only status in May 2026; the v1.3.0 build is stable and the integration with CWA is settled, but new feature work upstream has paused. If you want to pin for reproducibility, use
ghcr.io/calibrain/shelfmark:v1.3.0instead of:latest.
See examples/.env.example for the complete environment-variable reference and defaults.
If /config or /calibre-library lives on a network share, set:
- NETWORK_SHARE_MODE=trueThis:
- Disables SQLite WAL mode (NFS and SMB don't reliably support it; without this you'll see "database is locked").
- Skips the recursive ownership-fix at startup (slow on NFS, often fails on SMB).
- Switches the ingest watcher from inotify to polling (network-FS inotify events are unreliable).
Tested and supported. Ingest is a few seconds slower; everything else behaves the same.
If files end up owned by root after a copy: this build chowns files back to your
PUID:PGIDafter each metadata-change cycle, but if you've copied files in as root before upgrading, run once:docker exec calibre-web chown -R abc:abc /calibre-library(replaceabcif you've customized the user).
If you want to open the same library in calibre desktop while calibre-web-nextgen is running, set both:
- NETWORK_SHARE_MODE=true
- DESKTOP_COMPAT_MODE=trueBy default, calibre-web-nextgen holds a single SQLite connection open for the life of the process. That blocks calibre desktop from opening the library — on calibre 9.9.0 + macOS it crashes without an error dialog. DESKTOP_COMPAT_MODE=true switches to per-request connections so the file lock is released between web requests, letting calibre desktop open the database in the gaps.
Changes you make in calibre desktop (edits, adds, deletes) appear in the web UI on the next page load — no restart needed.
Trade-offs:
- Each web request pays a small extra overhead to open and close the database connection.
- If calibre desktop is actively writing when a web request comes in, the request waits up to 60 seconds for the lock. Heavy simultaneous use can slow the web UI.
- Designed for home-server use where calibre desktop is opened occasionally for bulk edits, not for concurrent heavy use of both.
calibre-web-nextgen doesn't ship any Calibre plugins, but it can load ones you install yourself — the same plugin .zip files Calibre desktop uses. This is how you add things like DRM removal (DeDRM, Obok) or .acsm fulfillment (the ACSM Input plugin): you supply the plugins, and they run automatically during ingest, library conversion, and metadata embedding.
-
Turn the feature on in your compose environment, then restart:
- CWA_CALIBRE_USER_PLUGINS=true -
Copy the plugin
.zipfiles into thepluginsfolder inside your config volume — from the host that's<your config folder>/.config/calibre/plugins/(the folder is created automatically once the option is on). -
Restart the container again. Each plugin is registered at startup; confirm with:
docker logs calibre-web 2>&1 | grep "Registered Calibre plugin"
Plugins that need keys or an account (DeDRM wants your device keys, ACSM Input wants an Adobe login) keep their settings in files next to the zips. Easiest path: configure the plugin in Calibre desktop on your computer first, then copy its settings files (e.g. plugins/dedrm.json, the plugins/DeACSM/ folder) from your desktop Calibre configuration folder into the same container plugins/ folder and restart.
To add another plugin after the first batch is registered, drop the zip in the same folder and run:
docker exec -e HOME=/config calibre-web /opt/calibre/calibre-customize -a "/config/.config/calibre/plugins/<plugin file>.zip"
The feature is off by default because it runs third-party plugin code inside your container — only install plugins you trust, from their official release pages. Which plugins are appropriate to use is your call.
To deploy CWA behind a reverse proxy, configure your reverse proxy to forward
requests to the CWA service and handle the path prefix (e.g. /cwa/). For
instance, with Nginx:
location /cwa/ {
proxy_pass http://calibre-web-automated:8083/;
proxy_set_header Host $http_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;
}
The trailing slash on proxy_pass matters: it strips /cwa/ before the request
reaches CWA. The headers are written out rather than pulled in with
include proxy_params; because that file ships with Debian and Ubuntu's nginx
package only — the official nginx Docker images don't have it, and nginx
refuses to start when the include is missing.
You must also configure the application with the external URL prefix by setting the following environment variable in your Docker compose file:
environment:
- PROXY_SCRIPT_NAME=/cwaLeave the trailing slash off PROXY_SCRIPT_NAME — CWA joins it to each path
directly, so /cwa/ would generate doubled-slash URLs.
This ensures that CWA correctly generates URLs when it is served from the prefix path instead of the web server root.
For TLS, upload limits, and the larger proxy buffers Kobo sync needs, see
examples/nginx-reverse-proxy.conf — the
settings there apply to a prefixed deployment too.
Behind multiple proxies (e.g. Cloudflare Tunnel then nginx then CWA), set the proxy count:
- TRUSTED_PROXY_COUNT=2Without this, CWA may see different client IPs across requests and trigger Session Protection warnings, forcing re-login on every page load. It can also mistake an externally secure OIDC callback for plain HTTP. Default is 1.
TRUSTED_PROXY_COUNT applies one trust depth to X-Forwarded-For,
X-Forwarded-Proto, X-Forwarded-Host, and X-Forwarded-Prefix. If your
proxy chain appends or replaces those headers at different layers, override the
first three independently; each falls back to TRUSTED_PROXY_COUNT, then 1:
- PROXYFIX_X_FOR=2
- PROXYFIX_X_PROTO=1
- PROXYFIX_X_HOST=1The corresponding ProxyFix arguments are x_for, x_proto, and x_host.
Count only proxies you control and that overwrite or sanitize the corresponding
header.
Hardcover is a free metadata provider. To enable it:
-
Sign up at https://hardcover.app and grab an API token at https://hardcover.app/account/api.
-
Add to your compose env:
- HARDCOVER_TOKEN=eyJhbGciOiJIUzI1NiI...Or paste it into Admin → Edit Basic Configuration → Hardcover API Key in the UI.
-
Restart the container.
Hardcover then appears in the Fetch Metadata modal.
If you set the token through the HARDCOVER_TOKEN environment variable, the Hardcover API Key field in the admin UI stays empty — that field only shows a key entered through the UI, and an environment-supplied token is not echoed back into the page. The admin page identifies whether HARDCOVER_TOKEN or HARDCOVER_TOKEN_FILE is active without displaying its value; a key typed into the field overrides either environment source.
To keep the token out of your compose file entirely, point HARDCOVER_TOKEN_FILE at a file containing just the token (docker-secrets style):
- HARDCOVER_TOKEN_FILE=/run/secrets/hardcover_tokenPrecedence: UI-configured key → HARDCOVER_TOKEN → HARDCOVER_TOKEN_FILE.
Enable the server-wide integration once under Admin → Edit Basic Configuration → Enable Hardcover Sync. This single switch controls both scheduled Hardcover ID fetching and Kobo/KOReader reading-progress sync. A declarative deployment can override it instead:
- HARDCOVER_SYNC_ENABLED=trueAccepted true values are true, 1, yes, and on; false values are false, 0, no, and off (case-insensitive). When the variable is set, the UI shows the effective state but leaves changes to the deployment configuration.
CWA has built-in KOReader progress sync; no separate kosync server is needed.
- In KOReader, install the CWA plugin: visit
http://your-cwa:8083/kosyncfor download and install instructions. - Point the plugin at
http://your-cwa:8083and log in with your CWA username and password. - Read on any device. Progress syncs back to CWA, and from there to Kobo if Kobo sync is enabled.
Keeping the plugin updated. KOReader's Updates Manager and appstore.koplugin can both update the plugin in place. Point either at the plugin's own repository, new-usemame/cwngsync.koplugin — not at this one. The plugin publishes a release only when the plugin itself changes, and its version is the server version it last changed in, so it can legitimately sit behind your server version; that alone doesn't mean anything is wrong. With the plugin repository configured, a check that reports no new release means the plugin stream has nothing newer.
If your update manager is still pointed at this repository, switch it. That setup keeps working — a release that changes the plugin attaches the plugin download — but the plugin only appears on those releases, which is easy to misread as "no update available". The download on /kosync always serves the plugin bundled with your running server if you would rather update by hand.
Matching filenames across devices (OPDS downloads). If you download books to KOReader over OPDS and sync progress by filename across several e-readers, turn on Use server filenames in KOReader's OPDS catalog settings (the checkbox when you add or edit the catalog). By default KOReader names a downloaded file Author - Title.epub from the catalog entry, which differs from the on-disk library name Title - Author.epub and forces a manual rename. CWA already sends the library name in the download's Content-Disposition header; with Use server filenames on, KOReader uses that name, so the file matches your library and your other devices without renaming.
Read your CWA library on a Kobo e-reader, with reading progress syncing both ways. Sync runs against your own server, so your library never leaves your network.
- In Admin → Edit Basic Configuration, turn on Enable Kobo sync.
- Open your user page (Admin → Users → your user, or your own profile) and click Create/View next to Kobo Sync Token. The dialog shows the exact
api_endpoint=line for your account. - Plug the Kobo into a computer over USB and open
.kobo/Kobo/Kobo eReader.confin a text editor. Add or replace theapi_endpoint=line with the one from the dialog, save, and eject the device cleanly. - On the Kobo, sync. Books on your Kobo Sync shelves appear on the device, and progress flows back to CWA.
api_endpointroutes library sync. Your highlights and notes travel over a separate reading-services channel governed by a different key,reading_services_host.You should not normally need to touch that key. CWA advertises the right value during sync initialization, and a device that performs a full initialization against your server adopts it on its own. That is the supported path.
🚨 Do not hand-edit
reading_services_hostin the conf file. Doing so has been measured to break syncing outright on at least one device — a Kobo Clara BW on firmware 4.42.23291 began failing every sync withFailedSync / WebRequestErr, and recovered only when the key was set back toreadingservices.kobo.com. A Kobo Libra Colour on 4.45.23697 is unaffected and routes annotations through CWA happily, so this is not universal — but we cannot yet predict which devices tolerate it, and the failure leaves you with a reader that will not sync and no obvious cause.If your sync has already broken after editing that key: set
reading_services_hostback toreadingservices.kobo.com, save, eject cleanly, and sync again.To see whether annotations are reaching CWA, make a highlight on the device, sync, and watch:
docker logs -f calibre-web 2>&1 | grep -iE "annotations|reading services"Silence means your highlights are going to Kobo's servers rather than yours. The safe way to change that is to get the device to perform a full initialization against CWA — re-generate the Kobo Sync Token and re-pair — rather than editing the key by hand.
This matters because CWA's protection against a Kobo deleting its own highlights after a sync (upstream calibre-web#2610) works by answering that channel, and it cannot protect a request it never receives. Until the device is routing annotations through CWA, treat highlights made on it as device-only and back them up.
To confirm the device is reaching your server, watch the logs while you sync — you should see requests to /kobo/<token>/v1/...:
docker logs -f calibre-web 2>&1 | grep /kobo/Behind a reverse proxy (nginx, Nginx Proxy Manager, Caddy, Cloudflare Tunnel)
Kobo devices sync over HTTPS, so the api_endpoint has to be your public https:// address. Put a proxy with a valid certificate in front and point it at the container's plain HTTP port:
- Proxy target is
http://<container-host>:8083. The proxy terminates TLS on 443; the connection from the proxy to CWA stays HTTP. WebSocket support is not needed for Kobo sync. - Generate the token while visiting CWA through the HTTPS address, so the
api_endpoint=line the dialog shows already carries your public hostname. - If you stack proxies (for example Cloudflare Tunnel in front of nginx), set
TRUSTED_PROXY_COUNTto the number of proxies.
nginx buffer sizes (important for Kobo sync)
Kobo's /v1/library/sync response carries large headers (auth, sync tokens, library state). Nginx's default proxy_buffer_size (4 KB) and proxy_buffers (8 × 4 KB) are too small; the response is silently dropped before it reaches the device, and the Kobo shows "Sync failed, please try again" with no error in the CWA log. The nginx error log shows upstream sent too big header while reading response header from upstream. Add these to the location / block proxying CWA:
proxy_buffer_size 32k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;(Larger libraries may need 128k / 4 256k / 256k.) Reload nginx after the change. On Synology DSM, the built-in reverse-proxy GUI doesn't expose these directives — drop a custom config at /etc/nginx/conf.d/http.calibre_web.conf that mirrors the DSM entry plus the buffer lines, then disable the DSM entry. DSM rewrites nginx.conf on reboot, so a Task Scheduler boot-event job that runs nginx -s reload reapplies the custom file. Nginx Proxy Manager users: add the three lines under the proxy host's Advanced tab.
See examples/nginx-reverse-proxy.conf for a complete reference snippet.
If you keep a Kobo account signed in
Signing into a Kobo account, or doing a factory reset, can rewrite the api_endpoint= line back to Kobo's own server, which sends sync to Kobo instead of your library. After signing in, re-check the conf line over USB and set it back if it changed. Many sideloaded setups sign out of the Kobo account so the device stops resetting the endpoint.
To keep the Kobo Store and your library working at the same time, turn on Proxy unknown requests to Kobo Store in Admin → Edit Basic Configuration. With it off (the default), any request CWA doesn't recognize gets an empty response — fine for a sideload-only device, but store features won't load.
Fixed in v4.0.13 and later. If you're still seeing it after upgrading, you probably have root:root-owned book directories from a pre-fix install. Run once:
docker exec calibre-web chown -R abc:abc /calibre-libraryFixed in v4.0.14 and later. Upgrade the image.
Almost always one of these:
- The device isn't reaching your server. The
api_endpoint=line in.kobo/Kobo/Kobo eReader.confmust point at your CWA address (notstoreapi.kobo.com), and that address must be reachable over HTTPS. See Kobo sync. - A Kobo account is signed in and Proxy unknown requests to Kobo Store is off, so the device's store calls get an empty response mid-sync. Turn that setting on, or sign out of the Kobo account on the device.
- Behind a reverse proxy, the proxy can't reach the container. Confirm the proxy target is
http://<host>:8083and that the certificate is valid. - nginx is silently dropping the sync response because its default buffers are too small for Kobo's library-sync headers. The CWA log shows the request arriving but nothing else; the nginx error log shows
upstream sent too big header. Addproxy_buffer_size 32k; proxy_buffers 4 32k; proxy_busy_buffers_size 64k;to the proxy location. See the nginx buffer sizes note in the Kobo sync section.
If your library is on a network share, set NETWORK_SHARE_MODE=true (see above). On local disk, this usually means a previous container shutdown was unclean: restart Docker, then the container.
Set TRUSTED_PROXY_COUNT to match your proxy depth. See Reverse proxy.
Three common causes:
- Files owned by root. Make sure ingest files are owned by your
PUID:PGIDuser. - Watcher missed them. Click the Refresh Library button on the navbar; it does a one-shot scan.
- Format isn't allowed. Check Admin → CWA Settings → Ingest for your allowed formats.
The defaults are admin / admin123 (lowercase). If you've already changed the password and forgotten it: stop the container, delete config/app.db, and restart. This resets the database. User accounts are lost; the library itself is untouched.
Check the issue tracker or open a new issue. Useful information:
- The version:
docker exec calibre-web printenv CWA_INSTALLED_VERSION - Recent logs:
docker logs calibre-web 2>&1 | tail -50 - What you did and what you expected to happen
| Behavior | Upstream CWA :latest |
This build |
|---|---|---|
| Cover saves from Hardcover/Google Books/iTunes/Open Library | Returns "not a valid image" | Saves and persists |
| Generate Kobo Auth Token | Blank page | Works |
| Safari metadata search | Silent 400 | Works |
| Safari book-delete button | Broken since the Feb-4 commit | Works |
Kobo bookmark sync with missing Location |
Crashes | Tolerates |
/kobo_auth/generate_auth_token IDOR |
Open (any user can mint another user's token) | Closed |
| Reverse-proxy user-profile updates | Drops path prefix | Honors getPath() |
Docker healthcheck on / → /login 302 |
Trips on curl -f |
Uses dedicated endpoint with service health checks |
.cbr / .cbz OPDS mimetypes |
Non-IANA | IANA-compliant |
| Cover resolution on high-DPI readers | Often 290×475 (Hardcover thumbnail) | 1000×1500+ via booster |
Admin routes (cwa_logs, convert, epub_fixer, …) |
14 unauthenticated | All require admin |
| Translations: ja, fr, cs, hu, zh_Hans, zh_Hant | Open in PRs | Merged |
Backports are conservative. Anything that touches auth, schema, or dependencies gets a manual review before merging.
The interface ships with the locales below. Completion is auto-refreshed on every push to main by scripts/generate_translation_status.py; to contribute a translation, edit the .po file under cps/translations/ for your language and open a PR.
| Language | Completion | Strings | Fuzzy |
|---|---|---|---|
| English (source) | 100% | source | — |
Italian (it) |
████████████████████ 100% |
3128/3128 | 0 |
Spanish (es) |
████████████████████ 99% |
3108/3128 | 0 |
Russian (ru) |
██████████████████░░ 90% |
2813/3128 | 0 |
French (fr) |
█████████████████░░░ 83% |
2602/3128 | 125 |
Polish (pl) |
████████████████░░░░ 82% |
2575/3128 | 0 |
German (de) |
██████████████░░░░░░ 69% |
2155/3128 | 12 |
Dutch (nl) |
██████████████░░░░░░ 69% |
2148/3128 | 289 |
Hungarian (hu) |
██████████░░░░░░░░░░ 52% |
1636/3128 | 119 |
Portuguese (Brazil) (pt_BR) |
█████████░░░░░░░░░░░ 45% |
1396/3128 | 306 |
Chinese (Traditional, Taiwan) (zh_Hant_TW) |
█████████░░░░░░░░░░░ 44% |
1371/3128 | 181 |
Japanese (ja) |
████████░░░░░░░░░░░░ 42% |
1310/3128 | 244 |
Slovenian (sl) |
████████░░░░░░░░░░░░ 38% |
1203/3128 | 313 |
Chinese (Simplified, China) (zh_Hans_CN) |
███████░░░░░░░░░░░░░ 37% |
1166/3128 | 343 |
Korean (ko) |
██████░░░░░░░░░░░░░░ 30% |
939/3128 | 266 |
Arabic (ar) |
█████░░░░░░░░░░░░░░░ 25% |
784/3128 | 281 |
Slovak (sk) |
█████░░░░░░░░░░░░░░░ 24% |
744/3128 | 308 |
Portuguese (pt) |
████░░░░░░░░░░░░░░░░ 22% |
696/3128 | 355 |
Galician (gl) |
████░░░░░░░░░░░░░░░░ 22% |
672/3128 | 356 |
Indonesian (id) |
████░░░░░░░░░░░░░░░░ 22% |
673/3128 | 357 |
Swedish (sv) |
████░░░░░░░░░░░░░░░░ 19% |
581/3128 | 383 |
Greek (el) |
███░░░░░░░░░░░░░░░░░ 16% |
504/3128 | 394 |
Czech (cs) |
███░░░░░░░░░░░░░░░░░ 15% |
475/3128 | 403 |
Ukrainian (uk) |
███░░░░░░░░░░░░░░░░░ 14% |
444/3128 | 368 |
Norwegian (no) |
███░░░░░░░░░░░░░░░░░ 14% |
429/3128 | 431 |
Vietnamese (vi) |
███░░░░░░░░░░░░░░░░░ 14% |
422/3128 | 352 |
Finnish (fi) |
██░░░░░░░░░░░░░░░░░░ 11% |
355/3128 | 383 |
Turkish (tr) |
██░░░░░░░░░░░░░░░░░░ 9% |
289/3128 | 380 |
Khmer (km) |
█░░░░░░░░░░░░░░░░░░░ 7% |
207/3128 | 340 |
- Bug reports: open a bug issue. Reproduction steps, version tag, and a
docker logssnippet help a lot. - Feature requests: open a feature issue. The bar is low — bug reports get prioritized for code work, but feature requests shape what gets looked at when the bug queue is quiet, and they help upstream see what users actually want. Don't worry about whether it's "in scope"; just file it.
- Pull requests: welcome. The merge bar is "doesn't break anything that currently works." Changes touching auth, schema, or dependencies get a closer review. Backports keep the original author's handle in the commit message.
- CWA PR authors with stalled work upstream: if you'd like your PR shipped here too, open an issue or send the PR our way.
Governance: GOVERNANCE.md. Contributing details: CONTRIBUTING.md.
Since May 2026: 188 releases, 673 merged pull requests, 262 issues closed, and 155 contributors credited by name.
This build exists to ship community bug fixes on a fast, regular release cadence — real fixes, written by real people, packaged so they reach users quickly. It complements the upstream projects it builds on, and their maintainers have our respect and our credits below.
Nothing here is paywalled and nothing ever will be. No sponsor-only features, no private Discord, no early access, no "pro" tier. Every line is GPL-3.0 and free whether you contribute or not. Sponsorship supports one thing: keeping this going.
If the project has been useful to you, a few dollars is a fair trade and genuinely appreciated. If it hasn't, that's completely fine — it stays free either way.
- GitHub Sponsors — one-time or monthly. GitHub takes 0%, so all of it arrives.
- Ko-fi — the same thing, if you already have an account there.
The most useful thing you can do costs nothing: file a bug when something breaks. That helps more than a few dollars does.
The codebase itself is Calibre-Web and Calibre-Web-Automated — written over many years by their human maintainers and contributors, who are credited in Credits. What this fork adds on top — its own fixes, their regression tests, the changelog and most issue replies — is largely produced by an AI assistant working from a written brief, with human review gates: merges require CI plus a regression test verified to fail without the fix, and anything adding a dependency, changing a licence or introducing an external URL is decided by a person.
The shipped application itself contains no AI: no model dependency, no inference call, no telemetry, and your library is not sent anywhere.
Built on:
- Calibre-Web-Automated (@crocodilestick and contributors) — the core software this build is based on. Original PR authors are credited by handle in every backport commit.
- Calibre-Web (@janeczku and contributors) — the web UI underneath CWA.
- Calibre (@kovidgoyal) — the library underneath all of it.
Every backported patch is credited to its original author by GitHub handle in the commit message and in CHANGES-vs-upstream.md.
If this build is useful to you, see Supporting the project. To support the upstream project it builds on, @crocodilestick has a Ko-fi too.
License: GPL-3.0-or-later. See LICENSE.
