Error finder: look up self-hosting error messages
Container won't start, certificate missing, mail stuck? Search your error message – every answer comes from a setup we got running ourselves.
Every card here comes from a tutorial on this site – from an error we actually ran into while
setting things up on the test server. Type the error message (address already in use,
NXDOMAIN, Unauthorized) or describe the symptom in your own words.
264 findings from real setups
The logs say „middleware … does not exist" and the service answers with 404.
Almost always the
provider suffix is missing: a middleware defined in the file provider is called
Read on: Hardening Traefik →name@file from the
perspective of Docker labels. Without the suffix Traefik looks for it among the Docker labels – and
finds nothing. The same applies the other way round for name@docker.BasicAuth rejects the correct password.
The
Read on: Hardening Traefik →$ characters in the bcrypt hash were interpreted as
variables by Docker Compose. In Compose they have to be doubled ($$2y$$05$$…). docker compose config shows the value that really arrives.The rate limit doesn’t seem to work.
Sequential
Read on: Hardening Traefik →curl calls are too slow: every process opens a
new connection, so you stay below the limit. Test in parallel, e.g. with
seq 40 | xargs -P 8 -I{} curl … – then the 429s appear.After setting the IP allowlist you get 403 yourself.
Traefik does not see your IP but that of the
proxy in front of it (Cloudflare, load balancer). Check with
Read on: Hardening Traefik →traefik/whoami what X-Forwarded-For
contains, and use ipallowlist.ipstrategy.depth for multi-stage setups.A v2 configuration with ipWhiteList stops working after the upgrade.
In Traefik v3 the middleware
is called
Read on: Hardening Traefik →ipAllowList; the old name was removed, so the rule no longer applies. Rename every
occurrence when migrating.The browser still refuses plain HTTP even though you fixed the configuration.
That is HSTS, not a
bug: the browser remembers
Read on: Hardening Traefik →max-age and enforces HTTPS even when your server no longer offers it. A
private window or a different browser helps for testing – and that is why the warning about preload
exists above.Traefik answers with 404 although the container is healthy.
Usually
Read on: Dockge: Manage Compose Stacks in the Browser →traefik.http.services.dockge.loadbalancer.server.port=5001 is missing. Inside the container Dockge
listens on 5001; without that line Traefik guesses. Also check that the container is attached to the
proxy network.The deployment aborts with „no such file or directory".
The host and container paths of the stacks
directory do not match. It must be mounted as
Read on: Dockge: Manage Compose Stacks in the Browser →/opt/stacks:/opt/stacks with
DOCKGE_STACKS_DIR=/opt/stacks – Dockge has the host’s Docker daemon do the work, and that daemon
only knows host paths.A stack is visible but all buttons are missing.
Then it lives outside
Read on: Dockge: Manage Compose Stacks in the Browser →/opt/stacks and Dockge
shows „This stack is not managed by Dockge." Move the folder as in step 7 – Dockge neither adopts it
by itself nor makes a copy.The stack name is rejected.
Only lowercase letters, digits and hyphens are allowed, because they
become the folder and Compose project name.
Read on: Dockge: Manage Compose Stacks in the Browser →My App won’t do, my-app will.After a restart the login is gone.
Then
Read on: Dockge: Manage Compose Stacks in the Browser →./data was not mounted persistently – that is where the
database and JWT secret live. Check that /opt/dockge/data exists and is bound in the Compose file.The interface is in the wrong language.
Dockge follows the browser and falls back to English for
unknown combinations; the settings let you pin the language.
Read on: Dockge: Manage Compose Stacks in the Browser →The library stays empty after you create it.
The first scan does not run automatically – that is
not a bug but how Audiobookshelf behaves. Trigger the scan in the library view. If it stays empty
afterwards, check with
Read on: Audiobookshelf: Self-Host Audiobooks & Podcasts →docker compose exec audiobookshelf ls -R /audiobooks whether the
container sees the files at all, and with ls -ln /opt/audiobookshelf/audiobooks that everything is
owned by UID 1000.Four MP3 files become four audiobooks instead of four chapters.
The files sit directly in the
author folder instead of a shared title folder. Audiobookshelf groups by
Read on: Audiobookshelf: Self-Host Audiobooks & Podcasts →Author/Title/ – move the
files into a subfolder named after the book and scan again.Traefik answers with 404 even though the container is running.
Usually
Read on: Audiobookshelf: Self-Host Audiobooks & Podcasts →traefik.http.services.abs.loadbalancer.server.port=80 is missing. Inside the container
Audiobookshelf listens on 80; without that line Traefik guesses and misses. Also check that the
container really is attached to the proxy network.The address jumps to /audiobookshelf/.
That is normal: the frontend ships with this fixed
router path, and
Read on: Audiobookshelf: Self-Host Audiobooks & Podcasts →ROUTER_BASE_PATH changes nothing at runtime because the path is baked into the
image. https://YOUR_DOMAIN remains a valid entry point.After switching devices the language is back to English.
The language choice lives locally in the
browser, not on the account. Set it once per device via your username →
Read on: Audiobookshelf: Self-Host Audiobooks & Podcasts →Language.The podcast is not found.
The search queries the iTunes catalogue; a typo or a missing entry
there leads to „no search results". Use the podcast’s RSS URL instead – the same field accepts both.
Read on: Audiobookshelf: Self-Host Audiobooks & Podcasts →Redirect loop (ERR_TOO_MANY_REDIRECTS) or mixed-content warnings.
The HTTPS detection is missing. Check that the
Read on: Running WordPress Cleanly with Docker & Traefik →WORDPRESS_CONFIG_EXTRA block with HTTP_X_FORWARDED_PROTO and the $$ signs is set exactly (see the warning in step 2), and restart the WordPress container.WordPress won’t start, reports “Error establishing a database connection”.
Usually a race condition or a password mismatch. Check that
Read on: Running WordPress Cleanly with Docker & Traefik →depends_on: condition: service_healthy is set and WORDPRESS_DB_PASSWORD matches MARIADB_PASSWORD exactly. Logs: docker compose logs db.The first start fails even though everything runs later.
The database needs a few seconds to initialize on the very first start. The health check covers this; without it, another
Read on: Running WordPress Cleanly with Docker & Traefik →docker compose up -d helps.Media uploads fail for large files.
PHP limits the upload size. Create your own
Read on: Running WordPress Cleanly with Docker & Traefik →uploads.ini and mount it to /usr/local/etc/php/conf.d/ with e.g. upload_max_filesize = 64M and post_max_size = 64M.After a domain change the site points nowhere.
WP_HOME/WP_SITEURL are hard-wired in the Compose file – for a new domain, change them there and restart the container (the values override the database setting).Login fails with a CSRF error.
LD_CSRF_TRUSTED_ORIGINS: https://YOUR_DOMAIN is missing or wrong (must be given with https:// and no path). Add it and docker compose up -d (see the warning in step 1).The page doesn’t load (Traefik 404) even though the container is running.
The health check isn’t
Read on: linkding: Self-Host Your Bookmarks →healthy yet – Traefik deliberately doesn’t route then. Wait about 30 seconds after start; check the status with docker inspect -f '{{.State.Health.Status}}' linkding-linkding-1.Title/description aren’t fetched automatically.
The target page blocks automated access or responds too slowly. You can then simply enter title and description by hand – the fields are in the same form.
Read on: linkding: Self-Host Your Bookmarks →The admin account wasn’t created.
LD_SUPERUSER_NAME/LD_SUPERUSER_PASSWORD only take effect on the first start with an empty database. If the data database already exists, a later change does nothing. Create the account afterwards: docker compose exec linkding python manage.py createsuperuser.The extension won’t connect.
Almost always the API token or the URL. Regenerate the token under Settings → Integrations and enter exactly
Read on: linkding: Self-Host Your Bookmarks →https://YOUR_DOMAIN as the server.The library stays empty.
Either there are no files in the mounted
Read on: Navidrome: Stream Your Own Music Like Spotify →music folder, or the permissions are wrong. Check: docker compose logs | grep -i scan shows tracksImported. If it’s 0, check the path and that the music folder is owned by UID 1000 (see Users & permissions).Tracks appear unsorted or without cover art.
The files’ metadata is incomplete. Navidrome sorts by tags, not by filenames – re-tag the files (e.g. with Picard) and let Navidrome rescan.
Read on: Navidrome: Stream Your Own Music Like Spotify →New music doesn’t show up.
The scan only runs hourly (
Read on: Navidrome: Stream Your Own Music Like Spotify →ND_SCANNER_SCHEDULE). Trigger an immediate scan via the refresh icon at the top of the web interface, or wait for the next interval.The app won’t connect.
Almost always the server URL: it must be
Read on: Navidrome: Stream Your Own Music Like Spotify →https://YOUR_DOMAIN (with https://, no path). Also check that username/password are exactly right – some apps additionally require enabling Subsonic compatibility, which is on by default in Navidrome.Playback stutters on large FLAC files over mobile.
The bandwidth isn’t enough for the original. Enable transcoding for the mobile player (see the tip in step 7).
Read on: Navidrome: Stream Your Own Music Like Spotify →Container won’t start: “unable to open database file”.
DB_NAME: /data/hc.sqlite is missing or the data folder isn’t owned by UID 1000. Set the path and chown -R 1000:1000 /opt/healthchecks/data (see the warning in step 2).The page responds with HTTP 500.
Most common cause: a non-ASCII character in
Read on: Healthchecks setup →SITE_NAME (or another text environment variable). Switch to pure ASCII and restart. To diagnose, temporarily set DEBUG: "True" – but switch it back to False afterwards.Login fails / CSRF error on submit.
CSRF_TRUSTED_ORIGINS: https://YOUR_DOMAIN must be set – Django otherwise rejects POST requests behind the reverse proxy. And ALLOWED_HOSTS must contain your domain exactly.The check won’t turn “green” even though the job runs.
Check whether the
Read on: Healthchecks setup →curl ping actually runs and hits the right UUID: curl -v https://YOUR_DOMAIN/ping/UUID should return OK. On the detail page the log shows whether and from which IP pings arrive.I get no notification on “down”.
No integration channel is assigned, or (for email) the SMTP settings are missing. Set up a channel under “Integrations” and assign it to the check.
Read on: Healthchecks setup →The page doesn’t load (Traefik 404) even though the container is running.
The health check isn’t
Read on: Stirling-PDF: The PDF Toolbox on Your Own Server →healthy yet – Traefik deliberately doesn’t route then. Wait 20–30 seconds after start; check the status with docker inspect -f '{{.State.Health.Status}}' stirling-stirling-pdf-1.Login with admin/stirling fails.
Either the password was already changed (then use the new one), or the config in the
Read on: Stirling-PDF: The PDF Toolbox on Your Own Server →configs volume is inconsistent. To reset, stop the app and check the user config in the configs folder; if in doubt clear the folder (note: this resets all settings).OCR can’t find my language.
The matching
Read on: Stirling-PDF: The PDF Toolbox on Your Own Server →.traineddata is missing from the tessdata volume. Get the file from the Tesseract language packs and place it in the data folder (see step 5).A conversion (e.g. Office → PDF) fails.
Such conversions need extra tools (LibreOffice) that are only included in the larger image variants. For the full feature set use the
Read on: Stirling-PDF: The PDF Toolbox on Your Own Server →-fat variant of the image (stirlingtools/stirling-pdf:2.14.2-fat).Large files cause errors or long waits.
OCR and conversion are memory-intensive. On a small VPS RAM can run short – then either process smaller files or move to a larger product.
Read on: Stirling-PDF: The PDF Toolbox on Your Own Server →The container won’t start / restart loop with permission denied.
The permissions on the
Read on: Syncthing setup →config or data folder are wrong. chown -R 1000:1000 /opt/syncthing/config /opt/syncthing/data and restart (see the warning in step 1).The interface gives a Traefik 404 even though the container is running.
The health check isn’t
Read on: Syncthing setup →healthy yet – Traefik deliberately doesn’t route then. Wait up to 60 seconds after start and check the status (see the warning in step 3).Two devices won’t connect.
Check three things: is port 22000 (TCP+UDP) open in the firewall? Did you enter the ID on both devices (pairing is mutual)? And is the ID exact (typos are caught easily thanks to a built-in checksum)? A “Disconnected” state with correctly entered IDs almost always points to the firewall port.
Read on: Syncthing setup →A folder is stuck at “Syncing 0%”.
Usually the share confirmation is missing on the other side, or the folder ID doesn’t match – it must be identical on both devices. Also check the write permissions in the target folder.
Read on: Syncthing setup →No access after setting the GUI password.
Forgot the password? You can reset it in
Read on: Syncthing setup →config.xml (<gui> block) by removing the <user> and <password> lines and restarting Syncthing – then the interface is reachable without a login again (and you set it anew immediately).The wizard reports write-permission errors on the data folder.
The container runs as
Read on: FreshRSS: Your Own RSS Reader →www-data (UID 33). With a bind-mounted ./data the permissions must match: chown -R 33:33 /opt/freshrss/data (see Users & permissions).Feeds don’t refresh automatically.
Check whether
Read on: FreshRSS: Your Own RSS Reader →CRON_MIN is set and the internal cron is running: docker compose logs freshrss | grep -i cron. You can trigger a refresh anytime with the refresh button at the top right or docker compose exec -u www-data freshrss ./cli/actualize-user.php --user admin.A feed stays empty / is marked “inactive”.
The source doesn’t serve valid RSS/Atom, or the URL is an HTML page instead of the feed. Many sites hide the feed – search the page source for
Read on: FreshRSS: Your Own RSS Reader →application/rss+xml. Under “Statistics → Inactive feeds” you’ll find problem sources gathered.Login fails even though the password is correct.
FreshRSS encrypts the password in the browser (JavaScript). If JavaScript is disabled or the connection isn’t really HTTPS (certificate warning), the login fails. Via Traefik HTTPS is guaranteed – check the certificate if in doubt.
Read on: FreshRSS: Your Own RSS Reader →Articles are missing after an update.
FreshRSS purges old articles after a configurable period (default: a few weeks). That’s intentional and keeps the database small; raise the retention per feed in its settings if needed.
Read on: FreshRSS: Your Own RSS Reader →systemctl start fails, status shows status=203/EXEC.
systemd couldn’t execute the program from
Read on: Understanding systemd →ExecStart; the journal says Unable to locate executable. Either the path is wrong, or the file is missing its executable bit (chmod +x). Without a leading /, systemd only searches /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin (check with systemd-path search-binaries-default) – so your own script under /opt or /root needs an absolute path.systemd warns “The unit file, source configuration file or drop-ins of … changed on disk”.
You edited a unit file, but systemd doesn’t pick up changes automatically. After every change under
Read on: Understanding systemd →/etc/systemd/system/: systemctl daemon-reload.The timer doesn’t show up in list-timers.
It wasn’t enabled.
Read on: Understanding systemd →systemctl enable --now SERVICE.timer – and remember the timer triggers the service, so you need both files (.service and .timer).systemctl enable answers “The unit files have no installation config”.
The unit is missing the
Read on: Understanding systemd →[Install] section with WantedBy=; in systemctl status you spot it by the static in the Loaded: line. Without [Install] systemd doesn’t know when to start the unit automatically – for timers it needs WantedBy=timers.target.The journal is huge / eats disk.
By default it grows in bounds, but you can trim it:
Read on: Understanding systemd →journalctl --vacuum-time=14d deletes entries older than 14 days, journalctl --disk-usage shows the consumption.“Host validation failed” instead of the dashboard.
The most common trap:
Read on: Homepage dashboard setup →HOMEPAGE_ALLOWED_HOSTS is missing or has the wrong domain. Enter exactly the domain you access it under, then docker compose up -d.The resource widgets show no or wrong values.
Homepage reads CPU and RAM from within the container (no socket needed). If
Read on: Homepage dashboard setup →disk: / shows the container filesystem instead of the host disk, mount the desired path as an additional volume (e.g. - /:/host:ro and disk: /host).An icon doesn’t load.
The name doesn’t match the icon catalog. Write the service name lowercase and without spaces (
Read on: Homepage dashboard setup →nextcloud.png), or place a custom image in the icons folder (mounted to /app/public/icons) and reference it as /icons/name.png.The container tile shows no status.
The value at
Read on: Homepage dashboard setup →container: must be the exact container name. Find it with docker ps --format '{{.Names}}' – with Compose it’s usually folder-service-1 (e.g. traefik-traefik-1).Changes to the YAML files don’t take effect.
A syntax error aborts the parsing. YAML is indentation-sensitive – check the logs with
Read on: Homepage dashboard setup →docker compose logs homepage for error lines and use consistent spaces (no tabs).You get “Authentication error – Auth is disabled or misconfigured” instead of the sign-in form.
Then
Read on: Homepage dashboard setup →HOMEPAGE_AUTH_ENABLED is set but the secret or the password arrives empty in the container – usually because the .env sits in the wrong folder or is missing a line. The log says Password auth is enabled but required settings are missing; docker compose config shows you which values Compose actually substitutes.The container stays unhealthy.
Almost always the IPv6 trap from step 4: the health check queries
Read on: Host your website with Hugo →localhost but nginx only listens on IPv4. Switch to http://127.0.0.1/. Check with docker inspect --format '{{.State.Health.Status}}' CONTAINER.Traefik shows 404 page not found (instead of your site).
That’s the Traefik 404, not the nginx one – Traefik finds no matching router. Most common causes: the container isn’t
Read on: Host your website with Hugo →healthy yet, the Host() rule has the wrong domain, or the proxy network isn’t external. See the 502/404 chapter in Understanding Docker networks.All links and images are broken.
The
Read on: Host your website with Hugo →baseURL in hugo.toml doesn’t match the real domain. Hugo bakes absolute URLs based on this value – fix it and rebuild.A post doesn’t appear.
Check the
Read on: Host your website with Hugo →date in the front matter: if it’s in the future, Hugo won’t build the post (see the warning box in step 3). A draft: true also hides content in a normal build.theme "PaperMod" not found during the build.
The theme folder is missing from the build context – usually because it’s a Git submodule that wasn’t copied, or a
Read on: Host your website with Hugo →.dockerignore excludes it. Store the theme as a real folder as in step 1.Container can’t reach another by name (bad address).
Both aren’t on the same named network, or one uses the default
Read on: Understanding Docker networks →bridge (no DNS). Check which networks a container is on with docker inspect -f '{{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}' CONTAINER.Traefik returns 502 Bad Gateway.
Almost always Traefik and the app are on different
Read on: Understanding Docker networks →proxy networks because the network was created twice. Make sure external: true is set in Compose and check with docker network inspect proxy whether both containers are listed.network proxy declared as external, but could not be found on up.
The external network doesn’t exist yet. Create it once:
Read on: Understanding Docker networks →docker network create --ipv6 --subnet fd00:cafe::/64 proxy. Do not let Traefik create the network itself – it then comes up without IPv6, and behind the proxy IPv6 visitors show up as the gateway address instead of their real IP.Database has internet despite internal.
It’s also attached to a non-internal network (e.g.
Read on: Understanding Docker networks →proxy). A container has the sum of the access of all its networks – the DB belongs only on the internal network, never on the proxy network.Two stacks collide on the subnet.
Docker assigns
Read on: Understanding Docker networks →172.x ranges automatically, but with many networks the pool can run short (could not find an available, non-overlapping IPv4 address pool). Clean up unused networks: docker network prune.Permission denied even though the permissions look right.
Check the permissions of the parent directory: if it’s missing the
Read on: Understanding Linux Users, Groups & File Permissions →x bit, you can’t reach the file at all, no matter what its own permissions are. ls -ld /path/to/folder shows it.docker ps still says “permission denied” after usermod -aG docker.
The new group isn’t active in the current session yet – log out and back in (see the warning box in step 7).
Read on: Understanding Linux Users, Groups & File Permissions →id in the new session must show docker.New files have unexpected permissions.
That’s set by the
Read on: Understanding Linux Users, Groups & File Permissions →umask – it subtracts permissions from the defaults. The Debian default umask 0022 produces 644 for files and 755 for directories. Check it with the umask command (no argument).A container won’t write to the volume.
The process inside the container runs under a specific UID (often not root). Set the owner of the host directory to match:
Read on: Understanding Linux Users, Groups & File Permissions →chown -R 1000:1000 ./data – the image’s docs name the correct UID (environment variables like PUID/PGID).usermod dropped the user from groups.
You used
Read on: Understanding Linux Users, Groups & File Permissions →-G without -a. -G replaces the secondary groups. Always use usermod -aG. Repair: re-add the missing groups with usermod -aG group1,group2 user.The search returns nonsense after the model switch.
The photos are still (or partially) indexed with the old model – old and new vectors don’t mix. Start the All run under Job Queues → Smart Search and wait until the queue is empty. Exactly this mixed state also occurs if you save the setting but forget the re-index.
Read on: Optimizing Immich (Part 2) →With an nllb-… model the search is no better than before.
Known behaviour in v3.1.0: the language hint these models expect cannot be set in Immich at all (see the warning box in step 3). Switch to an
Read on: Optimizing Immich (Part 2) →XLM-… or SigLIP2 model and re-index.The ML container exits with exit 137 after the model switch.
Out of memory: the new model no longer fits next to the server, the database and your other services. Either pick a smaller model or add RAM – the XLM base model really uses around 2 GB for the ML container alone.
Read on: Optimizing Immich (Part 2) →The external library stays empty after the scan.
Almost always the path is wrong: the import path must be the container path (
Read on: Optimizing Immich (Part 2) →/mnt/archive), not the host path. Check with docker compose exec immich-server ls /mnt/archive whether the container sees the files at all – if nothing shows up, the volume entry is missing or you skipped docker compose up -d after the change.The phone only backs up while the app is open.
On Android, battery optimization kills the background worker (exempt the app, see step 1); on iOS, Background App Refresh is off or iOS deprioritizes the app – opening it more often genuinely helps. And: by default, backup runs on Wi-Fi only – photos taken on the go are only “missing” until you’re back home.
Read on: Optimizing Immich (Part 2) →Browser shows NET::ERR_CERT_AUTHORITY_INVALID, curl says self-signed certificate.
The server is serving Traefik’s built-in fallback certificate (
Read on: How HTTPS works →TRAEFIK DEFAULT CERT) because issuance failed. Almost always: the A/AAAA record points to the wrong IP (check with dig YOUR_DOMAIN) or port 80 is closed in the firewall – the HTTP-01 challenge never arrives. The exact cause is in the Traefik logs: docker compose logs traefik | grep -i acme.The issuer says (STAGING) Ersatz Emmer YR2.
Let’s Encrypt’s test CA is still active – browsers distrust it on purpose. Remove the staging line from the Traefik configuration, empty
Read on: How HTTPS works →acme.json, restart the container (details in the Traefik tutorial).certificate has expired.
Automatic renewal has been failing for at least 30 days – same list of causes as the first error, just long unnoticed. If the message appears on one device only: check that device’s system clock, TLS is time-sensitive.
Read on: How HTTPS works →Let’s Encrypt reports too many certificates already issued.
Rate limit hit, usually from trial-and-error loops against the production CA. Switch to the staging CA until your setup works – and back up
Read on: How HTTPS works →acme.json instead of re-issuing certificates.unable to get local issuer certificate on an old client.
The system is missing the root certificate from step 3 – its root store is outdated. On Debian:
Read on: How HTTPS works →apt update && apt install ca-certificates.The container won’t start, the log says “listen udp :53: bind: address already in use”.
systemd-resolved (or another DNS service) occupies port 53. disable the stub listener as in step
1, then docker compose up -d again.docker compose up fails with “cannot assign requested address” for 10.8.0.1.
the WireGuard
interface
Read on: AdGuard Home: network-wide ad and tracking blocker →wg0 with 10.8.0.1 doesn’t exist (yet) – Docker can’t bind the port to a non-existent
address. first set up the WireGuard VPN and bring wg0 up
(ip -br addr show wg0 must show 10.8.0.1), then start the container.After the setup, the web interface is no longer reachable.
in the wizard, the admin port was set
to 80 (the suggestion) instead of 3000 – but Traefik forwards to 3000. in
Read on: AdGuard Home: network-wide ad and tracking blocker →./conf/AdGuardHome.yaml under http: correct the address to 0.0.0.0:3000 and docker compose restart.DNS doesn’t filter, even though the device is connected.
the browser uses DNS-over-HTTPS
(DoH) and thus bypasses your server completely – Firefox and Chrome have this active by default in
part. disable “Secure DNS” / “DNS over HTTPS” in the browser settings. You can check with
Read on: AdGuard Home: network-wide ad and tracking blocker →dig @10.8.0.1 … (always takes effect).A website is suddenly broken (empty pages, missing images, no login).
overblocking – a block
list blocks a domain the site really needs. find the blocked domain in the query log, allow it
via right-click/menu (exception) or disable the overly aggressive list.
Read on: AdGuard Home: network-wide ad and tracking blocker →The runner restarts and reports “cannot ping the docker daemon … certificate is valid for docker, …, not fjr-docker”.
The DinD service is named something other than
Read on: Forgejo Actions: your own CI/CD runner with Docker →docker, but its TLS
certificate is issued for docker. name the DinD service exactly docker (as above) and
address it via DOCKER_HOST: tcp://docker:2376 – then the name matches the certificate.The job starts but fails at actions/checkout with a connection error.
The runner was
registered with an internal instance URL (
Read on: Forgejo Actions: your own CI/CD runner with Docker →http://forgejo:3000). The job containers in DinD
can’t resolve this name. re-register with the public URL https://YOUR_DOMAIN (delete the
.runner file in the volume first, or recreate the volume).The runner doesn’t appear in the overview at all / the registration fails.
Wrong or already-used
token, or the runner can’t reach Forgejo. get a fresh token (step 1) and check that the runner
container reaches
Read on: Forgejo Actions: your own CI/CD runner with Docker →https://YOUR_DOMAIN (docker compose run --rm runner wget -qO- https://YOUR_DOMAIN/api/healthz).A job stays “pending” forever.
No runner has a matching label. The workflow uses
Read on: Forgejo Actions: your own CI/CD runner with Docker →runs-on: docker, so the runner must carry the label docker. check the labels when registering; the runner
overview shows the labels per runner.actions/checkout can’t find the action.
Forgejo loads actions from a configured registry (by
default
Read on: Forgejo Actions: your own CI/CD runner with Docker →data.forgejo.org). If the server is completely cut off from the internet, that fails.
allow outbound HTTPS access or mirror actions in an internal registry.mail-tester shows “reverse DNS does not match”.
The PTR entry is missing, not propagated yet, or
doesn’t match the A record. Set the PTR in the provider panel to
Read on: Email deliverability →mail.YOUR_DOMAIN and check with
dig -x; make sure mail.YOUR_DOMAIN points forward to the same IP.DKIM fails (DKIM: FAIL or “no signature”).
The DNS record was adopted incorrectly (genuinely
truncated key, wrong selector) or not propagated yet. Copy the value exactly from the mail server
UI, match the selector in the record (
Read on: Email deliverability →SELECTOR._domainkey) with the one configured in the server,
then counter-check with dig. That dig shows the key as two quoted blocks is normal and not
an error – see step 3.SPF “permerror” or “too many DNS lookups”.
Several SPF records, or too many nested
Read on: Email deliverability →include:
(limit: 10 DNS lookups). Check with dig +short TXT YOUR_DOMAIN | grep spf1 that really
only one line comes back, consolidate to one SPF record and remove unnecessary include:.Mails to Outlook/Hotmail land in spam or get rejected.
If you get a hard rejection with
Read on: Email deliverability →550 5.7.515 Access denied, sending domain … does not meet the required authentication level, it’s
not a reputation problem but Microsoft’s authentication rule from 5,000 mails per day – SPF and
DKIM must pass and an aligned DMARC record (at least p=none) must exist; the threshold sticks to
the domain even if you later send less. If the mails only land in the junk folder, Microsoft is
simply strict with new IPs: patience, send little but regularly, and if needed use Microsoft’s
SNDS/JMRP program.Your IP is on a block list.
The IP was with a spammer before you, or a mailbox of yours sends
spam. Check on the common block-list checkers, and on a legitimate hit have it removed via the
respective delisting form – and fix the cause (compromised account).
Read on: Email deliverability →The web interface on port 8080 doesn’t respond.
The container is still booting or port 8080 is
occupied/blocked. Check
Read on: Stalwart mail server →docker compose ps, read docker compose logs and make sure the
firewall lets port 8080 (and later 443) through. If the log
output stays completely empty after the setup, the log destination is still set to Log file –
switch it to Console in the console under Settings → Telemetry → Tracers and restart.I missed the bootstrap password.
It’s only logged once. Set a
Read on: Stalwart mail server →STALWART_RECOVERY_ADMIN=admin:YOUR_PASSWORD under environment: in the compose.yaml and restart
with docker compose up -d – that gives you a fixed recovery-admin account.No TLS certificate, the HTTPS address shows a warning.
Either Let’s Encrypt can’t reach your
server on port 443 (Stalwart uses the TLS-ALPN-01 challenge over 443), or one of the five hostnames
from step 1 is missing in DNS. The log names the culprit:
Read on: Stalwart mail server →ACME authentication error … NXDOMAIN looking up A for autoconfig.YOUR_DOMAIN. Add the missing
records, open port 443 in the firewall – Stalwart retries the certificate request automatically
afterwards.Mails to the outside stay stuck, logs show timeouts on port 25.
Your provider blocks outbound
SMTP traffic. At netcup the default policy “netcup Mail block” (ports 25/465/587) is to blame: in
the SCP under Firewall → Policies, take that netcup template off the server – no ticket needed,
see netcup firewall setup. Without it, no delivery to other
servers is possible.
Read on: Stalwart mail server →Other servers don’t accept your mails or they land in spam.
Missing or wrong PTR entry, no
SPF/DKIM/DMARC. That’s not a Stalwart error but a matter of DNS/reputation configuration – see the
email deliverability tutorial.
Read on: Stalwart mail server →generate_config.sh aborts with Cannot find command 'jq'.
the
Read on: Your own mail server with Mailcow: setup from scratch →jq package is missing. sudo apt install -y jq and start the generator again.The generator reports “User declined to create daemon.json” and aborts.
On hosts with active
IPv6 you answered the question about the IPv6-capable Docker configuration with
Read on: Your own mail server with Mailcow: setup from scratch →n – without it
mailcow won’t run there, and an open relay looms. Create /etc/docker/daemon.json containing
{"ipv6": true} (Docker 28 and newer), restart Docker with sudo systemctl restart docker and run
./generate_config.sh again – or simply confirm the question with Enter on the second attempt, then
the generator does both itself.The webmail only answers Unauthorized.
SOGo doesn’t know your mail domain: it reads the domain
list exclusively at startup and wasn’t restarted after the domain was created – its logs say
Read on: Your own mail server with Mailcow: setup from scratch →No authentication sources defined - nobody will be able to login. Restart SOGo via E-Mail → Restart
SOGo. If that doesn’t help, a failed login is still stuck in the cache: docker compose restart memcached-mailcow sogo-mailcow.No certificate, acme-mailcow keeps restarting.
Let’s Encrypt can’t reach your server on port
80, or the A record of
Read on: Your own mail server with Mailcow: setup from scratch →mail.YOUR_DOMAIN is wrong. check with docker compose logs acme-mailcow
whether the hostname and IP match, and that port 80 is open through UFW and the netcup firewall.Mails to the outside aren’t delivered, logs show timeouts on port 25.
Your provider blocks
outbound SMTP traffic (common as spam protection). At netcup it’s the default policy “netcup Mail
block” (ports 25/465/587): in the SCP under Firewall → Policies, take that netcup template off
the server – no support ticket needed, details in
netcup firewall setup. Without it you can’t send mail to
other servers – receiving and local delivery still work.
Read on: Your own mail server with Mailcow: setup from scratch →Ports 80/443 can’t be bound (address already in use).
A web server or reverse proxy (e.g.
Traefik) already runs on the host. For this recipe, mailcow belongs on its own server.
Alternatively, bind its nginx to a local port with
Read on: Your own mail server with Mailcow: setup from scratch →HTTP_BIND/HTTPS_BIND and
HTTP_PORT/HTTPS_PORT and put mailcow behind the proxy – that’s what
mailcow’s Traefik guide
describes. The mail ports stay directly on the host either way.Containers start slowly or are killed by the kernel (OOM).
Too little RAM. go to at least 6–8 GB
or disable ClamAV in
Read on: Your own mail server with Mailcow: setup from scratch →mailcow.conf (SKIP_CLAMD=y) – the virus scanner is the biggest memory
eater.The app reports “Cannot connect” or the web client stays empty.
The
Read on: ntfy setup →NTFY_BASE_URL doesn’t
match the called address. it must be exactly your public HTTPS URL (https://ntfy.YOUR_DOMAIN,
without a trailing slash). After a change, run docker compose up -d again.curl returns HTTP 401 or 403.
With
Read on: ntfy setup →deny-all, missing or wrong credentials are the most
common error. check the user/password (docker exec ntfy ntfy user list) and, when sending, supply
-u USER:PASSWORD or the bearer token. A 403 means the user exists but has no rights to this
topic – then grant rights with ntfy access.Messages arrive in the browser but not as push on the phone when the tab is closed.
browser push
needs granted notification rights and an active service worker; that’s unreliable once the tab is
closed. for real “on the go” push, use the ntfy app – it keeps the connection in the background.
Read on: ntfy setup →502 Bad Gateway from Traefik.
the container isn’t ready yet or listens on the wrong port.
check
Read on: ntfy setup →docker compose logs ntfy and that the label loadbalancer.server.port=80 is set – ntfy
listens on port 80 in the container (NTFY_LISTEN_HTTP=":80").429 Too Many Requests with many messages in quick succession.
ntfy limits the rate per sender
by default to prevent abuse. On a private server with your own scripts you rarely hit this – but a
loop script without a pause does. bundle messages instead of firing them every second, or raise the
limits specifically via the
Read on: ntfy setup →NTFY_VISITOR_* environment variables (described in the ntfy docs under
“Rate limiting”). NTFY_BEHIND_PROXY: "true" is a prerequisite so the limit applies per real IP
instead of per Traefik container.The container writes, but the bind-mount folder on the host stays empty.
You used a relative
path that points somewhere other than intended, or Docker created the path anew as an empty folder.
with
Read on: Docker volumes vs. bind mounts →docker run, always give bind mounts an absolute path (/opt/app/config, not config).
If this host path doesn’t exist yet, Docker silently creates it as an empty folder – so check with
ls that you really hit the right one. In the compose.yaml, ./ paths are perfectly fine, on the
other hand – they’re relative to the compose.yaml and thus unambiguous. Note also: docker run -v config:/data (without / or ./ in front) is not a bind mount, but creates a named volume
called config.Permission denied as soon as the container tries to write into the mount.
The process in the
container runs under a different UID than the owner of the host folder – typical with bind mounts.
with named volumes this rarely happens (Docker sets the permissions). With bind mounts, give the
folder to the matching user (
Read on: Docker volumes vs. bind mounts →chown -R 1000:1000 /opt/app/data) or use the user: setting of the
Compose in the image.After docker compose down all data is gone.
You used
Read on: Docker volumes vs. bind mounts →docker compose down -v – the -v
deletes the named volumes too. for a normal restart, work without -v. Use -v deliberately
only when you really want to start from scratch.docker system prune deleted data.
docker volume prune or docker system prune --volumes
removes volumes that no container is currently attached to. With docker volume prune, named
volumes are protected by default – only anonymous volumes are deleted; named ones only go with
--all/-a. (With docker system prune, on the other hand, -a/--all controls the images, not
the named volumes.) another strong argument for naming – an sk-demo-vol survives an accidental
docker volume prune, an anonymous volume doesn’t. Still, only run prune with volumes when all
important stacks are active, or remove specifically with docker volume rm.The browser shows a certificate error or 404 page not found.
Traefik hasn’t fetched the Let’s
Encrypt certificate yet, or the DNS record doesn’t point to the server. Check with
Read on: Self-hosting Matomo →dig stats.YOUR_DOMAIN that the IP is correct, and look at the Traefik logs. You find the exact
container name from your Traefik setup with docker ps | grep traefik, then docker logs <container-name> (for us e.g. docker logs traefik-traefik-1). The HTTP challenge fails if port 80
isn’t reachable from outside – check your firewall and the
netcup firewall.The installer reports SQLSTATE... Connection refused or hangs at the database.
MariaDB wasn’t
ready on the first start. Give the DB a moment and reload the page. Check with
Read on: Self-hosting Matomo →docker compose logs db whether it says ready for connections. If an access error appears instead, the DB_PASSWORD
in the .env and the already-created DB no longer match – then a clean restart with docker compose down -v (careful: deletes the data) and docker compose up -d helps.Warning “It looks like the trusted_hosts setting is not correct”.
Matomo checks, for security
reasons, under which hostname it’s called. The warning appears when you change the domain. Confirm
the correct hostname via the button in the message – Matomo then enters it in
Read on: Self-hosting Matomo →config/config.ini.php.No visits appear in the dashboard.
The tracking code is missing, incorrectly embedded, or you’re
visiting your own site (Matomo ignores you if your IP is excluded). Open your website in a private
window and check in the network tab whether a request to
Read on: Self-hosting Matomo →matomo.php goes out. In Matomo,
Administration → Diagnostic → Tracking failures helps.The dashboard loads very slowly.
The on-the-fly archiving computes on every call. Set up the
archiving cron from step 8 and switch report generation to cron.
Read on: Self-hosting Matomo →The container restarts repeatedly (Restarting), the log says bind: address already in use.
The built-in SSH server collides with itself because
Read on: Forgejo: your own Git server behind Traefik →SSH_PORT and SSH_LISTEN_PORT don’t match. Set both to the same value (here 2222) – then Forgejo starts cleanly.The container takes forever to become healthy.
By default, Docker runs the first healthcheck only after the
Read on: Forgejo: your own Git server behind Traefik →interval (30 s) – so the container looks “unhealthy” for 30 s+, even though Forgejo has long been ready in ~2 s. The solution is already in the Compose above: start_interval: 2s checks every 2 seconds during the startup phase and switches to healthy as soon as the app responds. (Requires Docker 25+ / Compose v2.20+ – given on Debian 13.)Traefik returns 502 Bad Gateway.
Almost always the wrong port: Forgejo’s web interface listens internally on 3000, so
Read on: Forgejo: your own Git server behind Traefik →loadbalancer.server.port=3000 must be set and the container must be on the proxy network.Clone links show localhost or the wrong port.
Then
Read on: Forgejo: your own Git server behind Traefik →ROOT_URL, SSH_DOMAIN or SSH_PORT are wrong. Correct the values in the Compose and restart with docker compose up -d.SSH clone fails with Permission denied (publickey).
The SSH server is running, but your public key isn’t stored in the account yet (step 6) – or you forgot the port
Read on: Forgejo: your own Git server behind Traefik →2222.The live verifier stays on “Waiting” / no hits in the dashboard.
Check in the browser (dev tools → Network) whether
Read on: HitKeep: self-host privacy-friendly web analytics →hk.js is loaded at all and the send request
afterwards comes back with status 2xx. Most common causes: the snippet isn’t in the HTML, the
site domain created in HitKeep doesn’t match the hostname of the visited page, or an ad/tracking
blocker filters the call. Since you host under your own domain (first-party), most blockers
don’t apply – but some lists know the path hk.js.All visitors seemingly come from a single IP, country and provider show “(Unknown)”.
Then
Read on: HitKeep: self-host privacy-friendly web analytics →HITKEEP_TRUSTED_PROXIES isn’t taking effect: the range you gave doesn’t contain your
proxy’s IP, and HitKeep only evaluates Traefik’s container IP. Check which subnet the proxy sits
in with docker network inspect proxy – 172.16.0.0/12 covers the usual Docker networks – and
restart the stack (docker compose up -d). After that the real client IP from the
X-Forwarded-For header counts again.Traefik returns 404 or 502.
A
Read on: HitKeep: self-host privacy-friendly web analytics →404 usually means the router rule isn’t matching – check that Host(...) contains your real
domain and the container is on the proxy network. A 502 indicates the wrong port: HitKeep
listens internally on 8080, so loadbalancer.server.port=8080 must be set.After login you land on the login page again (login loop).
That’s almost always a mismatch in
Read on: HitKeep: self-host privacy-friendly web analytics →HITKEEP_PUBLIC_URL: the value must match exactly the address
through which you call HitKeep (including https://, without a trailing slash). Correct the
variable and restart the container.The container won’t start or isn’t healthy.
Look at the logs:
Read on: HitKeep: self-host privacy-friendly web analytics →docker compose logs -f hitkeep. A missing or empty HITKEEP_JWT_SECRET is a
typical start blocker – generate one as in step 1 and enter it.A latest handshake never appears in wg show.
The UDP port isn’t reachable or the
keys don’t match. WireGuard is silent with wrong keys – there’s no error message,
just no handshake. Check:
Read on: Setting up a WireGuard VPN →sudo ufw status (port 51820/udp open?), the netcup firewall
(inbound UDP 51820), the Endpoint in the client (correct public IP?) and that the
private and public keys aren’t swapped.Handshake is there, but no internet arrives in the full tunnel.
Almost always the
routing/NAT. Is
Read on: Setting up a WireGuard VPN →net.ipv4.ip_forward=1 set (sysctl net.ipv4.ip_forward)? If Docker is
running – or UFW is active – the FORWARD DROP trap from the warning box above applies;
both set the forward policy to DROP. The rules must sit before the existing chains
with -I FORWARD 1. Is the external interface in the MASQUERADE correct (eth0 vs.
something else)?Full tunnel is up, but DNS queries still run past the tunnel (DNS leak).
Without a
Read on: Setting up a WireGuard VPN →DNS = line, your device keeps querying the DNS servers of your local network – on open
Wi-Fi the operator still sees which domains you visit. On Linux, check with resolvectl status which DNS server is assigned to the wg0 interface, or run a leak test (e.g. on
dnsleaktest.com): if foreign resolvers appear there, enter a real resolver in the
client config (DNS = 1.1.1.1) – or the server itself (10.8.0.1) if a dedicated DNS
resolver runs there.The tunnel is up, but large transfers (SSH, HTTPS, downloads) hang or break off.
An
MTU problem.
Read on: Setting up a WireGuard VPN →wg-quick sets the MTU to 1420; in some networks (DS-Lite, certain mobile
networks) that’s still too high. Try MTU = 1412 or 1280 in the [Interface] section
of the client.wg-quick up reports resolvconf: command not found.
The
Read on: Setting up a WireGuard VPN →DNS = line needs
resolvconf. Either install sudo apt install openresolv or remove the DNS line if
you don’t need the VPN DNS.The connection falls asleep as soon as the phone briefly sends nothing.
The client
sits behind NAT/CGNAT.
Read on: Setting up a WireGuard VPN →PersistentKeepalive = 25 in the client config keeps the
connection open.Your figures are well below ours, especially for the CPU.
A VPS shares the physical
CPU. Check the steal time (
Read on: netcup VPS 1000 G12 benchmarked: how fast is it really? →vmstat 1, column st) and top (line %st). If it’s
persistently high, neighbors on the same host are computing right now. Measure again at a
different time of day – often the difference is gone then.The disk figures are absurdly high (e.g. “10 GB/s random read”).
You’re missing
Read on: netcup VPS 1000 G12 benchmarked: how fast is it really? →--direct=1 – then fio measures the RAM cache, not the NVMe. Always test with direct
I/O, otherwise the numbers are worthless.lsblk shows ROTA=1 (“rotational”) for vda in the column – is that a hard disk
instead of NVMe?
No. That’s a virtualization artifact: the virtio driver reports the
virtual disk as rotational across the board. The measured 100k+ IOPS and 4 GB/s prove that
real flash storage is behind it.
Read on: netcup VPS 1000 G12 benchmarked: how fast is it really? →The download is much slower than 2 Gbit/s.
Measure against a nearby, fast server
(e.g. Falkenstein). A distant target or a slow counterpart limits the measurement, not your
VPS. A single
Read on: netcup VPS 1000 G12 benchmarked: how fast is it really? →curl stream also doesn’t always exhaust the full bandwidth.Every run delivers different numbers.
Normal – benchmarks fluctuate. Measure multiple
times, discard the first (“warm”) run and take the median. Also compare only the same
tool versions and parameters with each other.
Read on: netcup VPS 1000 G12 benchmarked: how fast is it really? →docker compose pull pulls no new image, even though a new version exists.
Your tag
points to a fixed version (
Read on: Keeping your whole Docker stack safely up to date →:1.37.2) or to a major tag (:18), under which there’d only be
a new major. pull only fetches what the same tag now points to. For a version jump
you have to bump the tag in the compose.yaml yourself.The app no longer starts after the update or throws database errors.
Usually a breaking
change or a failed migration. Check
Read on: Keeping your whole Docker stack safely up to date →docker compose logs, compare with the release notes.
Set the tag back to the old version and up -d; if the new version already migrated the
data, restore the backup from step 3.Diun reports nothing, even though updates exist.
Check that the containers carry the
label
Read on: Keeping your whole Docker stack safely up to date →diun.enable=true and Diun can read the socket (DIUN_PROVIDERS_DOCKER=true, socket
mounted). For newer version tags the container additionally needs diun.watch_repo=true
– without it, Diun only sees changes to the exactly pinned tag. And: whatever Diun sees for the
first time is only written to the database, not reported (watch.firstCheckNotif is
false) – to test the notification path, set DIUN_WATCH_FIRSTCHECKNOTIF=true once.The disk fills up, even though you regularly run docker image prune.
docker image prune only clears dangling images, not the old tagged versions. Remove them
specifically with docker rmi <image>:<tag> or – with care – docker image prune -a. The
build cache (docker builder prune) can grow too.On pulling you get toomanyrequests / a Docker Hub rate limit.
diun.watch_repo=true
on many images queries many tags. Set the watch interval less often (e.g. once daily) and
narrow with diun.include_tags to relevant versions instead of scanning the whole repo.A freshly set ban doesn’t take effect, the IP keeps getting 200.
The bouncer caches the
decisions and, in stream mode, pulls new ones only after the pull interval (up to ~60 seconds). Wait
briefly. Whoever wants to reduce the reaction time lowers
Read on: CrowdSec: modern, collaborative intrusion prevention →updateIntervalSeconds in the middleware
(step 3).cscli metrics show acquisition shows no line for the log file.
CrowdSec isn’t reading the
logs – and because
Read on: CrowdSec: modern, collaborative intrusion prevention →cscli omits empty tables, the line is missing entirely instead of showing
Lines read: 0. Most common cause: the container started before the access.log existed; then
docker logs crowdsec contains the line
No matching files for pattern /var/log/traefik/access.log, and CrowdSec won’t pick the file up
by itself later – cd ~/crowdsec && docker compose restart fixes it. Otherwise check
that Traefik really writes to /var/log/traefik/access.log (step 1), that both containers mount
the same host path, and that the acquis.yaml names exactly this path.After restarting Traefik every app returns 403 – including for you.
The bouncer couldn’t
reach the LAPI at startup and then blocks in doubt instead of letting traffic through unprotected.
The Traefik log says
Read on: CrowdSec: modern, collaborative intrusion prevention →crowdsecQuery:unreachable with the URL
http://crowdsec:8080/v1/decisions/stream. Check with docker ps that the crowdsec container is
Up – as soon as the next pull succeeds (up to ~60 seconds), requests go through again on their
own. That’s safe, but makes CrowdSec a critical component: if the container isn’t running, your
site is affected. restart: unless-stopped (set above) brings it back automatically after a server
reboot.CrowdSec bans lots of harmless or no real visitors – the detected IP is always your CDN’s.
If a
CDN like Cloudflare sits in front of Traefik, Traefik only sees its IP. Then have the real
client IP read from the
Read on: CrowdSec: modern, collaborative intrusion prevention →X-Forwarded-For header: fill forwardedHeadersTrustedIPs in the
middleware with the CDN networks – otherwise you end up banning the CDN. Directly at netcup (without
a CDN) there’s nothing to do here.docker compose up -d on Traefik reports a plugin error.
Traefik downloads the plugin from the
net on startup. Check that the server may go out (the netcup
firewall only blocks SMTP outbound, UFW nothing) and that
Read on: CrowdSec: modern, collaborative intrusion prevention →modulename and version are exactly right.CrowdSec bans nothing even though the attacks are in access.log – or it bans 172.18.0.1.
Then Traefik never sees the real source address in the first place. This happens with IPv6 visitors when the
Read on: CrowdSec: modern, collaborative intrusion prevention →proxy network was created without IPv6: Docker pushes the connection through a helper process and replaces the source address with the bridge gateway’s. Every IPv6 request then carries the same 172.x.x.x in access.log, and a ban on that either hits nobody or everybody. Recreate the network with --ipv6; the instructions are in Reverse proxy with Traefik.dd overwrote the wrong disk – data gone.
dd doesn’t ask and isn’t mockingly called
“disk destroyer” for nothing. Check the target (of=) always beforehand with lsblk,
and never confuse sda with sdb. There’s no undo – when in doubt, read it three times.kill -9 ended a service, but the database is corrupted afterwards.
-9 gives the
process no chance to close cleanly. Always first use kill without -9 and give the
process a few seconds. Only when it really hangs does the hard variant follow.rsync copied (or deleted) much more than expected.
The trailing slash decides:
Read on: The essential terminal commands for your server →rsync -a /source/ copies the contents of source, rsync -a /source copies the
folder along with it. And --delete deletes everything in the target that’s missing in
the source. Test with -n first, always.A tool reports “command not found”.
The package isn’t installed. Install it (see tip
box above) or check the name. Some tools like
Read on: The essential terminal commands for your server →iotop additionally need sudo to see any
data at all.After tmux, on the next login “everything is gone”.
You started a new session
instead of attaching.
Read on: The essential terminal commands for your server →tmux ls lists running sessions, tmux attach -t 0 attaches you to
the first. Just tmux on its own creates a fresh one every time.A target is at up = 0 or “DOWN” in Prometheus.
Prometheus can’t reach the exporter. For
Read on: Monitoring with Grafana & Prometheus →cadvisor, check that it’s on the same monitoring network and the name and port are exactly
right (cadvisor:8080). For the node job, check the extra_hosts entry on the Prometheus
service and the target host.docker.internal:9100 – without both, Prometheus doesn’t find the
node-exporter in the host network. After changes to the prometheus.yml, restart the
Prometheus container: docker compose restart prometheus.The cAdvisor dashboard partly shows “No data” or cAdvisor won’t start.
If the mounts or
Read on: Monitoring with Grafana & Prometheus →privileged: true are missing, cAdvisor doesn’t see the containers. Check the cadvisor block
(mounts, devices: /dev/kmsg). Individual “No data” panels are normal – some metrics (like CPU
throttling) only exist if you’ve set CPU limits on the containers.Grafana doesn’t load correctly behind Traefik – login fails or the layout is broken.
Almost
always
Read on: Monitoring with Grafana & Prometheus →GF_SERVER_ROOT_URL is wrong. It must be exactly the public address
(https://grafana.YOUR_DOMAIN). Also check that the Traefik label
loadbalancer.server.port=3000 is set – Grafana listens on 3000 internally.Grafana shows “No data” in the panels, even though the targets are up.
Usually the time
range. A fresh stack has no history yet – set “Last 15 minutes” at the top right and wait a few
minutes for data to accumulate. Also check that the Prometheus data source was selected on
dashboard import.
Read on: Monitoring with Grafana & Prometheus →The disk space on the server grows steadily.
That’s the Prometheus TSDB. Reduce
Read on: Monitoring with Grafana & Prometheus →--storage.tsdb.retention.time (e.g. to 15d) or monitor fewer targets. The prom_data
volume grows with the number of metrics × retention time.502 Bad Gateway on access, even though the container is running.
Traefik reaches the
container but hits the wrong port. Jellyfin listens on 8096 – check the label
Read on: Self-hosting Jellyfin →traefik.http.services.jellyfin.loadbalancer.server.port=8096 for typos. If a
504 Gateway Timeout comes back after a few seconds instead, the port isn’t the
problem, the network is: then networks: [proxy] is missing and Traefik is running against
an address it can’t reach at all.The libraries stay empty, even though files are there.
Almost always a permission
problem. The container runs as
Read on: Self-hosting Jellyfin →1000:1000 (step 4), so the media must belong to that
user: sudo chown -R 1000:1000 /mnt/media. And: in Jellyfin the container path must be
entered (/media/filme), not the host path. Afterwards start “Scan all libraries” under
Dashboard → Scheduled Tasks.Video stutters/buffers, docker stats shows Jellyfin at ~100% CPU.
It’s
transcoding. Under Dashboard → Playback the activity monitor shows whether it says
“Transcode” instead of “Direct Play”. Set the client quality to original, change exotic
codecs to a widely supported format, or give the server more (dedicated) cores.
Read on: Self-hosting Jellyfin →After a reboot the library is gone and Jellyfin shows empty folders.
The block storage
wasn’t mounted – usually a missing or wrong
Read on: Self-hosting Jellyfin →fstab entry (step 2). Check with df -h /mnt/media and sudo mount -a. The line must use the UUID, not /dev/vdb1.The login page loads, but the app can’t find the server / links point to an internal address.
JELLYFIN_PublishedServerUrl is missing or wrong. Set it in the compose.yaml
to https://jellyfin.YOUR_DOMAIN and restart with docker compose up -d (a restart isn’t
enough for the environment change to take effect).Uploads of large videos abort after about a minute (error 502 or 499).
Traefik’s
Read on: Self-hosting Immich →readTimeout (default 60 s) kicks in. Set it to 600s (or higher) as in step 4 and restart
Traefik. This is by far the most common Immich-behind-proxy error.The immich-machine-learning container crashes or exits with “exit 137”, search and face
recognition don’t work.
Too little RAM – the container was killed by the system (out of
memory). Give the server more memory, or disable ML by removing the service
Read on: Self-hosting Immich →immich-machine-learning from the compose.yaml. Immich then runs without face recognition
and smart search, but upload and timeline work normally.After an update the containers no longer start or report migration errors.
Server, machine
learning and database must run on the same version. Set the new
Read on: Self-hosting Immich →IMMICH_VERSION in the
.env and update all containers together (docker compose pull && docker compose up -d).
A downgrade after a migration isn’t possible – only a reset from the backup.Immich shows “maintenance mode” / “temporarily unavailable”.
Immich v3 starts into a
maintenance mode under certain database states. The log (
Read on: Self-hosting Immich →docker compose logs immich-server)
then contains a URL with a one-time token (…/maintenance?token=…) – through it you log
into the maintenance mode and choose “restart” or end it. After that the normal interface is
back.Container won’t start, “permission denied” on the database or upload directory.
UPLOAD_LOCATION or DB_DATA_LOCATION have the wrong access rights or are on a network
drive. Put both on a local disk and make sure Docker may write into them.On login, “Forbidden (403)” or “CSRF verification failed” appears.
PAPERLESS_URL is not
set or set wrong. It must exactly match your HTTPS address (https://paperless.YOUR_DOMAIN,
without a trailing slash). After the correction, docker compose up -d.Files in the consume folder aren’t processed, “permission denied”.
Most common cause: the
folders weren’t created beforehand (step 3), but generated by the Docker daemon on the first
start – then they belong to
Read on: Self-hosting Paperless-ngx →root. With sudo chown -R $(id -u):$(id -g) ~/paperless/consume ~/paperless/export they belong to you again. Otherwise: USERMAP_UID/USERMAP_GID don’t
match the folder’s owner – determine your ID with id -u and id -g, enter the values and
restart.Uploaded documents get stuck “in processing”.
The background processing runs via Redis.
Check that the
Read on: Self-hosting Paperless-ngx →broker container is running, and look under File Tasks for the error
message of the failed task.Text recognition returns gibberish or recognizes nothing.
Wrong OCR language. Set
Read on: Self-hosting Paperless-ngx →PAPERLESS_OCR_LANGUAGE=eng (or eng+deu for mixed documents). Only installed languages
work.Office documents (Word, Excel) aren’t accepted or end in a timeout.
Gotenberg and Tika
are responsible for those. Check that both containers are running and
Read on: Self-hosting Paperless-ngx →PAPERLESS_TIKA_ENABLED=1 plus the two endpoint variables are set.During bulk import the server gets very slow, the CPU is constantly maxed out.
OCR is
compute-intensive, and Paperless uses all cores by default. On small servers you can throttle
the load by limiting the number of workers or threads (
Read on: Self-hosting Paperless-ngx →PAPERLESS_TASK_WORKERS,
PAPERLESS_THREADS_PER_WORKER). Then the import takes longer, but the interface stays
usable.After the Traefik restart, the HSTS header doesn’t appear in the curl output.
Usually
the middleware isn’t on the router. Check that the line
Read on: Hardening Nextcloud (Part 2) →traefik.http.routers.nc.middlewares
names both middlewares (nc-dav,nc-secure) and that the two nc-secure header labels
are in the nc-app block. Then docker compose up -d nc-app. Traefik only picks up changed
labels when recreating the container.The email test fails (“There was a problem sending the email”).
Almost always
port/encryption or authentication. Combine
Read on: Hardening Nextcloud (Part 2) →SSL/TLS with port 465 or STARTTLS with port
587 – not crosswise. If your provider uses 2FA, you need an app password (see step 4).
Details are in the Nextcloud log: docker exec -u www-data nc-app php occ log:tail 20.You can’t get in yourself after enforcing 2FA.
You hadn’t set up a second factor yet. Lift
the requirement via the command line, set up your factor calmly and arm it again afterwards:
Read on: Hardening Nextcloud (Part 2) →docker exec -u www-data nc-app php occ twofactorauth:enforce --off.In the brute-force box a 172.x.x.x address appears instead of your real IP.
TRUSTED_PROXIES doesn’t match the actual proxy subnet. Determine it with docker network inspect proxy -f '{{(index .IPAM.Config 0).Subnet}}' and enter the value in the nc-app
environment (part 1, step 2). Otherwise the brute-force protection bans the proxy in an
emergency and thus all users.After a reboot, Nextcloud reports “Redis went away” or gets very slow.
The app container
started before Redis. Make sure
Read on: Hardening Nextcloud (Part 2) →nc-redis is in depends_on (part 1) and runs with restart: unless-stopped – then Docker catches the start-order case itself.Bad Gateway (502) when opening status.YOUR_DOMAIN.
Almost always the port label
Read on: Installing Uptime Kuma →traefik.http.services.kuma.loadbalancer.server.port=3001 is missing or has a wrong port.
Traefik then reaches the container but knocks on the wrong port. Check the label and run
docker compose up -d again.404 page not found instead of Kuma.
As with every app behind Traefik:
Read on: Installing Uptime Kuma →traefik.enable=true set? Container on the proxy network? Is the domain in the Host(...)
rule correct and does the DNS record status.YOUR_DOMAIN point to the server? The Traefik
dashboard shows under “HTTP Routers” whether kuma is registered.The interface loads, but the live update stutters / breaks off.
Kuma uses WebSockets.
Traefik forwards those correctly by default – if the problem still occurs, it’s usually an
upstream CDN/proxy (e.g. Cloudflare in “proxy” mode) that blocks WebSockets. For direct
operation behind Traefik, no extra configuration is needed.
Read on: Installing Uptime Kuma →After a re-setup all monitors are gone.
The
Read on: Installing Uptime Kuma →kuma-data volume was deleted (e.g. by
docker compose down -v). All configuration and history lives solely in this volume – that’s
why it’s at the top of the next section.“Access through untrusted domain” instead of the login page.
Your domain isn’t in
Read on: Self-hosting Nextcloud: your own cloud behind Traefik →trusted_domains. Check NEXTCLOUD_TRUSTED_DOMAINS in the Compose file. To set it
afterwards, use occ: docker exec -u www-data nc-app php occ config:system:set trusted_domains 1 --value=cloud.YOUR_DOMAIN.The security check reports “Your web server is not set up properly to resolve .well-known/caldav”.
The CalDAV/CardDAV redirect isn’t working. Check the
Read on: Self-hosting Nextcloud: your own cloud behind Traefik →nc-dav
middleware labels (step 3) and that the router includes them via
...routers.nc.middlewares=nc-dav. The regex must match the full https://… URL –
redirectregex checks the complete URL, not just the path.Warning “The ‘Strict-Transport-Security’ HTTP header is not set”.
The HSTS header is
missing. Set it as a Traefik middleware and attach it to the router:
Read on: Self-hosting Nextcloud: your own cloud behind Traefik →traefik.http.middlewares.nc-secure.headers.stsSeconds=15552000. Attach it additionally to
the nc-dav middleware on the router (...routers.nc.middlewares=nc-dav,nc-secure). The
header must come from the proxy, not from Nextcloud. In part
2 we set up exactly this nc-secure middleware
fully (including includeSubdomains).Large uploads break off or end with a timeout / “413”.
The PHP limit is too small.
Increase
Read on: Self-hosting Nextcloud: your own cloud behind Traefik →PHP_UPLOAD_LIMIT and PHP_MEMORY_LIMIT (step 3) and restart the container. In
rare cases these variables don’t take effect – then mount your own php.ini snippet into the
image.On the first start the installation aborts with “MySQL server has gone away” or “Connection refused”.
The app container was faster than the database. That’s exactly what the
Read on: Self-hosting Nextcloud: your own cloud behind Traefik →healthcheck with depends_on: condition: service_healthy is for – check that both are
present in your Compose file, and restart with docker compose up -d.The container exits again immediately, the log says No persistent volume!.
The
Read on: Vaultwarden: your own password manager behind Traefik →volumes: mapping is missing. Vaultwarden then aborts with the box “It looks like you did
not configure a persistent volume!” and exits with code 1, so your passwords don’t end up
in an ephemeral container. Add ./vw-data:/data as in step 2 and restart.The web interface shows “You need to enable HTTPS!” or the login fails with crypto errors.
Vaultwarden uses the browser’s Web Crypto API, which is only available in a
secure context (real HTTPS). You opened the page over
Read on: Vaultwarden: your own password manager behind Traefik →http:// or with an invalid
certificate. Make sure Traefik fetched a valid Let’s Encrypt certificate (check the Traefik
log) and that you reach the page over https://. The DOMAIN variable must also start with
https://.The admin panel rejects your password, even though it’s correct.
Probably the dollar
signs in the
Read on: Vaultwarden: your own password manager behind Traefik →ADMIN_TOKEN aren’t doubled. In the compose.yaml every $ must become $$.
Check with docker compose exec vaultwarden printenv ADMIN_TOKEN how the token really
arrives inside the container – there it has to be a single $ again. (docker compose config won’t help: it shows the file with the doubled $$.) Also remember: at login you
enter the password, not the hash.The phone app can’t find the server or reports “Server URL invalid”.
The server URL must
be the full
Read on: Vaultwarden: your own password manager behind Traefik →https:// address without a trailing path (https://vault.YOUR_DOMAIN). Also
check whether the domain is reachable from outside via a browser and the certificate is
valid – apps are stricter about certificate errors than browsers.Despite SIGNUPS_ALLOWED=false, someone was able to register.
The setting was probably
set in the admin panel and overrides the environment variable, or the container wasn’t
restarted after the change. Check the value under Settings → General settings in the
panel and restart with
Read on: Vaultwarden: your own password manager behind Traefik →docker compose up -d.subprocess ssh: Host key verification failed with the SFTP target.
Restic can’t
confirm the SSH host key of the backup server because it isn’t known yet. Connect
once manually (
Read on: Restic backups: encrypted and off-site →ssh BACKUP_USER@YOUR_BACKUP_HOST) and confirm the fingerprint – or
store it with ssh-keyscan YOUR_BACKUP_HOST >> ~/.ssh/known_hosts. After that Restic
runs through.wrong password or repository does not exist.
RESTIC_REPOSITORY or
RESTIC_PASSWORD_FILE isn’t set or points nowhere – so check the EnvironmentFile in
the systemd service. A manually set export only applies in the current shell.repository is already locked.
An aborted run left a lock. Check that really
no backup is running anymore, then
Read on: Restic backups: encrypted and off-site →restic unlock. Never unlock blindly while a run
is active in parallel.The backup gets huge / backs up nonsense.
Restrict it with
Read on: Restic backups: encrypted and off-site →--exclude/--exclude-file (caches, logs, temporary files) and use
--one-file-system so Restic doesn’t dive into mounted foreign filesystems.prune takes forever or was aborted.
No reason to panic –
Read on: Restic backups: encrypted and off-site →prune is resumable and
the repository stays valid. Start it again and afterwards run restic check once.
Separate --prune from the daily backup on large repos (see step 6).The timer runs, but a success message never arrives in Uptime Kuma.
Look at the
last run with
Read on: Restic backups: encrypted and off-site →journalctl -u restic-backup.service. Usually the script aborts
before the curl call (e.g. the DB dump failed) – thanks to set -euo pipefail it
then stops cleanly instead of reporting a broken backup as success. So the missing push
isn’t a bug, but exactly the alarm signal you want.You locked yourself out.
ignoreip was missing or contained the wrong IP. Connect
via the console in the netcup SCP (independent of SSH) and unban yourself with
sudo fail2ban-client set sshd unbanip YOUR_OWN_IP. Then enter your IP in ignoreip
and reload. That’s why the whitelist comes first in step 2.fail2ban.service won’t start (systemctl status shows “failed”).
Almost always
a typo in
Read on: Setting up Fail2ban →jail.local. Check the syntax with sudo fail2ban-client -t (test mode) –
the command names the faulty line.Your values from jail.local don’t take effect.
Bans only last 10 minutes, or your
own IP gets thrown out despite
Read on: Setting up Fail2ban →ignoreip. Almost always the reload is missing – the
service was already running before you wrote your first own line, and start/enable --now does nothing on a running service. What actually applies is shown by
sudo fail2ban-client get sshd bantime (600 = Debian default, 3600 = your hour) and
sudo fail2ban-client get sshd ignoreip; it is applied with
sudo systemctl reload fail2ban.No one is ever banned, even though the log is full of failed attempts.
Check with
Read on: Setting up Fail2ban →sudo fail2ban-client status sshd whether Total failed rises at all. If it stays at
0, the filter isn’t finding the entries – usually because an outdated guide set a
logpath to a non-existent file. On Debian 13, remove the logpath from
jail.local and use the journal (step 2).Bans “don’t work” – the IP keeps connecting.
On Debian 13, Fail2ban bans via
nftables (
Read on: Setting up Fail2ban →banaction = nftables from defaults-debian.conf). You can see it with
sudo nft list table inet f2b-table: it holds the chain f2b-chain and inside it an
address set addr-set-sshd with the banned IPs. sudo iptables -L -n | grep f2b, on the
other hand, returns nothing – that’s not a fault but the wrong tool layer, where
older guides get stuck. If the table is missing entirely, the interplay with the firewall
is stuck – restart the service and look at /var/log/fail2ban.log.“Certificate invalid” in the browser, or the Traefik log shows ACME errors.
The
three usual reasons: (1) The DNS record doesn’t point to the server yet – check
Read on: Setting up Traefik: reverse proxy with automatic HTTPS →dig +short YOUR_DOMAIN. (2) Port 80 isn’t reachable from outside (firewall/netcup
firewall) – the HTTP challenge needs it. (3) You hit the rate limit of the
production CA – switch to the staging server (tip in step 3), test, then go back.404 page not found when calling the app domain.
Traefik doesn’t know the route.
Check: Does the container have
Read on: Setting up Traefik: reverse proxy with automatic HTTPS →traefik.enable=true? Is it on the proxy network?
Is the domain in the Host(...) rule exactly right (incl. subdomain)? The dashboard
(step 7) shows under “HTTP Routers” whether the router was registered.The browser shows Traefik’s self-signed emergency certificate; the log reads
permissions 644 for /acme.json are too open, please use 600.
Traefik is running but
skipped the ACME resolver – hence no real certificate. Run
Read on: Setting up Traefik: reverse proxy with automatic HTTPS →chmod 600 acme.json (step
2) and restart the container.No app is routed; the Traefik log repeats client version 1.24 is too old. Minimum supported API version is 1.40.
Your Traefik version is too old for your Docker
engine – the Docker provider can no longer query the socket. Current Docker (Engine 29,
API level ≥ 1.40) needs Traefik ≥ v3.6; that’s why this tutorial uses
Read on: Setting up Traefik: reverse proxy with automatic HTTPS →traefik:v3.7. Reproduced against Docker 29: v3.5.6 runs into exactly this error,
v3.6.25 talks to the socket cleanly again. So older tags like v3.3 or v3.5 no
longer work with new Docker – bump the image tag and run docker compose up -d again.Basic auth on the dashboard is rejected immediately / the router is missing.
In the
Read on: Setting up Traefik: reverse proxy with automatic HTTPS →compose.yaml, the $ characters of the hash must be doubled ($$). Check the
hash once more outside with htpasswd -nbB.Gateway Timeout or Traefik doesn’t reach the container.
Usually the app is on the
wrong network, or Traefik doesn’t know which one is meant.
Read on: Setting up Traefik: reverse proxy with automatic HTTPS →providers.docker.network=proxy in Traefik and networks: [proxy] on the app must
match.502 Bad Gateway, even though the container is running.
Traefik reaches the
container but hits the wrong port. If the app doesn’t listen on 80, it needs the label
Read on: Setting up Traefik: reverse proxy with automatic HTTPS →traefik.http.services.<name>.loadbalancer.server.port=<real-port>. This exact case
meets you with the first app in the next tutorial (Uptime Kuma on 3001).The dry run reports No packages found that can be upgraded unattended.
Usually
perfectly normal – no security update is currently pending. Check the configuration
anyway via the
Read on: unattended-upgrades setup →Allowed origins are: line in the --debug run. If no security origins
appear there, the Origins-Pattern from step 3 isn’t right.Updates come, but the server never reboots despite a kernel update.
Usually
Read on: unattended-upgrades setup →Automatic-Reboot is set to "false" (default) – then set it to "true" and give it an
Automatic-Reboot-Time (step 3). If the option is right, check the marker file: without
/var/run/reboot-required the reboot never triggers. The package’s kernel hook is what
sets it – dpkg -S /etc/kernel/postinst.d/unattended-upgrades must report it (step 3)./boot fills up, updates fail.
Old kernels pile up. Set
Read on: unattended-upgrades setup →Remove-Unused-Kernel-Packages "true" (step 3); clean up once with sudo apt autoremove --purge.A package is stubbornly held back (kept back).
unattended-upgrades doesn’t
install updates that would remove other packages. You resolve such cases deliberately
by hand with
Read on: unattended-upgrades setup →sudo apt upgrade and check what happens.dig returns no IP or a wrong (old) one.
Usually still propagation. Query a
public resolver specifically to bypass your local cache:
Read on: Connecting a domain to your server (DNS basics) →dig +short YOUR_DOMAIN @1.1.1.1. If it already shows the correct value, only your local/provider cache
hasn’t expired yet – wait.The domain resolves, but to the wrong IP – e.g. a registrar’s parking page.
Often
an old A record or a redirect/parking setting still exists. Remove contradictory
entries; per name and type there should be exactly one value (deliberate
exceptions aside).
Read on: Connecting a domain to your server (DNS basics) →IPv4 works, IPv6 (AAAA) leads to timeouts.
You entered an AAAA record even though
the server has no working IPv6 address. Browsers then prefer IPv6 and run into a
timeout. Remove the AAAA record until the server really supports IPv6.
Read on: Connecting a domain to your server (DNS basics) →In some interfaces the value is stored with a trailing dot (YOUR_DOMAIN.) and
you’re unsure.
The trailing dot (root of the DNS hierarchy) is normal and correct –
many DNS managers add it automatically. No reason to worry.
Read on: Connecting a domain to your server (DNS basics) →Emails sent from the server land in spam or are rejected (the mail log shows e.g.
does not resolve to address or no PTR record).
The reverse-DNS entry (PTR) is
missing or doesn’t match the sender hostname. Set the PTR as described in step 5 at
the VPS provider and make sure it points to the same name that also resolves via the
A/AAAA record.
Read on: Connecting a domain to your server (DNS basics) →After activating, name resolution stops working (apt update hangs, ping domain.de
fails, but ping 1.1.1.1 works).
The DNS replies are being blocked. Check the rule in
the base template INBOUND UDP ACCEPT, Src port 53. Important: source port, not
destination port.
Read on: netcup firewall setup →The clock drifts, or TLS certificates are rejected due to the wrong time.
The NTP
replies are missing. Add INBOUND UDP ACCEPT, Src port 123.
Read on: netcup firewall setup →IPv6 no longer works (v4 does).
The ICMPv6 rule is missing. Without Neighbor
Discovery, the server can’t even find its neighbor (router) over IPv6.
Read on: netcup firewall setup →After activating, you can no longer get in via SSH.
The SSH rule is missing or names the
wrong port. Via the VNC console (SCP → Display) you can still reach the server; correct
the policy and save again. That’s exactly what the “keep a second session open” rule above
helps against.
Read on: netcup firewall setup →The server can’t send emails (outbound port 25 doesn’t work).
That’s not an error in
your policy, but netcup’s default policy “netcup Mail block” – it blocks outbound SMTP
(ports 25/465/587) as spam protection. For real mail sending you have to disable this netcup
template on the server.
Read on: netcup firewall setup →The server initially runs normally and is suddenly offline after one or two weeks.
If
the server uses DHCP, the lease renewal is blocked by the whitelist – the DHCP reply
comes from UDP source port 67, which no rule allows, and UDP is stateless. Add
Read on: netcup firewall setup →INBOUND · UDP · ACCEPT · Src 67 to the base template. Statically configured netcup VPS (the
default) aren’t affected.“Create” won’t make a snapshot while a DVD is inserted.
If an .iso image is attached
in the virtual drive under Media → DVD Drive, the SCP refuses new snapshots. Eject the
image there – the ISO row in the server overview has to show
Read on: netcup Server Control Panel: snapshots, console & rescue →- again – and start the
snapshot once more.The snapshot can’t be created because there isn’t enough disk space.
A snapshot needs
room next to your live data. Clean up on the server, hand the freed blocks back to the
host with
Read on: netcup Server Control Panel: snapshots, console & rescue →fstrim -av, delete snapshots you no longer need, and trigger the Storage
Optimization in the SCP under Media → Disks.After an online snapshot restore, the database is corrupted.
An online snapshot may
catch a running write mid-way. For servers with a database, take an offline snapshot
or create a database dump beforehand.
Read on: netcup Server Control Panel: snapshots, console & rescue →You can’t get into the SCP.
The SCP uses its own credentials (not SSH). They are
in the welcome email; a forgotten SCP password you reset in the netcup customer account
(CCP).
Read on: netcup Server Control Panel: snapshots, console & rescue →After ufw enable you can no longer get in via SSH.
The SSH rule was missing or
targeted the wrong port. Connect via the console in the netcup SCP (VNC,
independent of SSH), allow your SSH port there (
Read on: Setting up a firewall with UFW →sudo ufw allow …) and test again.
That’s exactly what step 3 warns about.ERROR: Could not find a profile matching 'OpenSSH'.
The OpenSSH profile only
exists if
Read on: Setting up a firewall with UFW →openssh-server is installed. Use the port number instead: sudo ufw allow 22/tcp (or your port). sudo ufw app list shows the available profiles.A Docker container is reachable from outside even though UFW doesn’t open the port.
Not your mistake – Docker bypasses UFW. Docker writes its rules directly
into
Read on: Setting up a firewall with UFW →iptables and inserts them ahead of the UFW chains. A published container port
(ports: in the compose.yaml) is thus open, no matter what UFW says. The clean
solution is a second firewall layer in front of the server – at netcup the
firewall in the SCP as an upstream
perimeter. On the host it also helps to bind container ports only to 127.0.0.1
instead of 0.0.0.0.yaml: line 7: did not find expected key (or similar YAML errors).
YAML is
indentation-sensitive – spaces only, never tabs, and consistent per level
(common: 2 spaces). Check the file without starting it:
Read on: Understanding Docker Compose →docker compose config
resolves everything and complains about exactly the wrong line.Error ... address already in use on up.
The host port (left in
Read on: Understanding Docker Compose →8080:80) is
already taken. Find the occupant with sudo ss -tlnp | grep 8080 or choose a
different host port. Two containers cannot share the same host port.An app can’t find its database (could not translate host name).
The hostname
must be the service name (e.g.
Read on: Understanding Docker Compose →db), not localhost. Inside a container,
localhost is the container itself, not the neighboring service. And: both services
must be in the same Compose project (the same file).After docker compose down all data is gone.
Either the volume wasn’t declared
as a named volume under
Read on: Understanding Docker Compose →volumes: (then it was only the ephemeral container
storage), or down -v was used. Always run persistent services with a declared named
volume.docker-compose: command not found.
That’s the old Compose v1 (with a hyphen).
The current one is
Read on: Understanding Docker Compose →docker compose (with a space, plugin). If it’s missing: sudo apt install docker-compose-plugin (see Docker
tutorial).E: Package 'docker-ce' has no installation candidate.
The package source from
step 2 is missing or malformed – apt only knows the name from the Debian packages'
dependencies and finds no package behind it. Check the contents of
Read on: Installing Docker on Debian →/etc/apt/sources.list.d/docker.list – it must contain your Debian version (e.g.
trixie) – and then run sudo apt update again.permission denied while trying to connect to the docker API at unix:///var/run/docker.sock.
The
Read on: Installing Docker on Debian →docker group membership hasn’t taken effect yet. End the SSH session and
reconnect; groups must then include docker. If not, repeat step 4.Conflicts during installation with already-present packages.
Another Docker
variant is already installed (
Read on: Installing Docker on Debian →docker.io, podman-docker, …). Remove the old
packages first: sudo apt remove docker.io docker-doc docker-compose podman-docker containerd runc – existing containers/images are preserved.docker compose reports docker: unknown command: docker compose.
The Compose
plugin is missing – Docker was probably installed differently earlier. Run
Read on: Installing Docker on Debian →sudo apt install docker-compose-plugin. Note: the old docker-compose (with a hyphen) is a
different, outdated tool.SSH keeps asking for the password despite ssh-copy-id.
Usually the file
permissions on the server are wrong: SSH ignores
Read on: Hardening SSH access →authorized_keys if group or
others may write to ~/.ssh. The log on the server (sudo journalctl -u ssh --since -5min) then shows Authentication refused: bad ownership or modes for directory /home/koch/.ssh. Fix it with chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys, and also check that you’re connecting with the right user
(koch@…, not root@…).Permission denied (publickey) – and you can’t get in at all anymore.
Password login was disabled before the key worked. No drama: open the VNC
console in the netcup SCP, log in locally there, set
Read on: Hardening SSH access →PasswordAuthentication yes, restart SSH and start again at step 2.After the restart the SSH service no longer starts.
Syntax error in the
Read on: Hardening SSH access →sshd_config – a mistyped PasswordAuthentification is enough (that’s why you
always run sshd -t before restarting). Log in via the VNC console; sudo sshd -t
then names file, line and option: /etc/ssh/sshd_config: line 125: Bad configuration option: PasswordAuthentification, followed by
/etc/ssh/sshd_config: terminating, 1 bad configuration options.The settings seem to take effect, but password login still works.
A file under
Read on: Hardening SSH access →/etc/ssh/sshd_config.d/ (often 50-cloud-init.conf) overrides your values.
sudo sshd -T | grep -i passwordauthentication shows the actually effective
setting – adjust matches in the extra files and restart SSH.ssh: connect to host … port 22: Connection timed out.
Usually the IP was
mistyped or the server isn’t fully provisioned yet. Check the IP in your customer
account and whether the server is shown as “online” there. Wait a few minutes
after ordering.
Read on: First steps with a netcup VPS →Permission denied, please try again on root login.
Wrong password – often a
copy-paste issue with invisible trailing spaces, or a different keyboard layout.
Copy the password without surrounding spaces directly from the credentials.
Read on: First steps with a netcup VPS →WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!.
The server was reinstalled
and has a new host key – SSH rightly raises the alarm. If you reinstalled it,
remove the old entry with
Read on: First steps with a netcup VPS →ssh-keygen -R YOUR_SERVER_IP and reconnect. If you
didn’t reinstall, investigate before you connect.sudo: command not found as the new user.
On minimal images the package is
sometimes missing. Install it as
Read on: First steps with a netcup VPS →root: apt install sudo. Then check that the
user is in the group: groups koch must contain sudo – otherwise repeat step 3
and log out and back in once.Nothing found. Try fewer words, or the literal error message.