netcup-dyndns-and-trusted-proxies-updater
This script is designed for users with a dynamic dual-stack address, using Netcup as their provider. It automatically updates your IPv6 address in the Trusted Proxy configuration for Nextcloud.
The script checks the current IPv4/IPv6 address of the host and updates the corresponding values at Netcup if necessary. Additionally, it updates the Trusted Proxies configuration in Nextcloud via the OCC CLI.
Prerequisites
This script needs Python 3.13 or newer.
Installation
Install the PyPI project with your preferred tool:
pipx install netcup-dyndns-and-trusted-proxies-updater
uv tool install netcup-dyndns-and-trusted-proxies-updater
pip install netcup-dyndns-and-trusted-proxies-updater
Updating
Update to the latest release with the same tool you used for the installation:
pipx upgrade netcup-dyndns-and-trusted-proxies-updater
uv tool upgrade netcup-dyndns-and-trusted-proxies-updater
pip install --upgrade netcup-dyndns-and-trusted-proxies-updater
To install a specific version instead, for example to roll back:
pipx install --force netcup-dyndns-and-trusted-proxies-updater==1.4.0
uv tool install --force netcup-dyndns-and-trusted-proxies-updater==1.4.0
pip install netcup-dyndns-and-trusted-proxies-updater==1.4.0
Confirm the installed version afterwards with netcup-dyndns --version. Updating
does not touch your settings file or cache directory. See CHANGELOG.md
for the changes in each release.
If you run the script from a cloned repository, update it with:
git pull
uv sync
Using an Installed Tool
All three installation methods provide the netcup-dyndns command. Confirm the
installed version and repository URL with:
netcup-dyndns --version
By default, the command creates .settings.json and .temp in the current
working directory. First inspect the paths it will use:
netcup-dyndns --show-paths
Run it once to create the settings file, edit the generated file with your credentials and domains, then run the updater again:
netcup-dyndns
For a service, cron job, or any other setup where the current directory is not stable, specify absolute paths explicitly:
netcup-dyndns \
--settings-file /etc/netcup-dyndns/settings.json \
--cache-dir /var/lib/netcup-dyndns
Use the same --settings-file and --cache-dir values for every run. The
cache directory stores the last observed IP addresses, retry state and login backoff.
You can also clone this repository to run it from source:
git clone <repository-url>
cd netcup-dyndns-and-trusted-proxies-updater
uv run src/netcup-dyndns.py
The first run will create a settings.json file and a temp folder in your project directory. 3. Configure the settings.json file with the following parameters:
{
"API_PASSWORD": "",
"API_KEY": "",
"CUSTOMER_ID": "",
"NETCUP_DOMAIN": "",
"NEXTCLOUD_PATH": "",
"TRUSTED_PROXIES_POS": "",
"PARALLEL_PROCESSES": 1,
"IP_MODE": "both",
"DISABLE_NEXTCLOUD_NGINX": false,
"RATE_LIMIT_BACKOFF_MINUTES": [10, 30, 60],
"NTFY_SERVER": "https://ntfy.sh",
"NTFY_TOPIC": "",
"NTFY_TOKEN": ""
}
API_PASSWORD: Your Netcup API password.
API_KEY: Your Netcup API key.
CUSTOMER_ID: Your Netcup customer ID.
NETCUP_DOMAIN: The domain name(s) you want to update, separated by commas (e.g., example.com, example.net).
NEXTCLOUD_PATH: The file path to your Nextcloud instance (required unless DISABLE_NEXTCLOUD_NGINX is true).
TRUSTED_PROXIES_POS: The position in the TrustedProxies configuration where the new IP address should be added (e.g., the first, second, etc.; required unless DISABLE_NEXTCLOUD_NGINX is true).
PARALLEL_PROCESSES: Number of parallel threads for DNS updates (Default: 1 for sequential execution. Values > 1 enable the ThreadPoolExecutor).
IP_MODE: Determines which IP types to update. Options are "both" (default), "ipv4", or "ipv6".
DISABLE_NEXTCLOUD_NGINX: Set to true to disable all Nextcloud OCC and Nginx reload tasks. Useful if you only want to use the script as a pure DynDNS client (Default: false).
RATE_LIMIT_BACKOFF_MINUTES: Minimum waiting times in minutes between login attempts after netcup refused a login because of too many logins (Default: [10, 30, 60]). See Login Limits and ntfy Notifications.
NTFY_SERVER: The ntfy server used for notifications (Default: https://ntfy.sh).
NTFY_TOPIC: The ntfy topic that receives a notification when the login is still refused after the last backoff step. Leave empty to disable notifications (Default: empty).
NTFY_TOKEN: Optional ntfy access token for protected topics, sent as Authorization: Bearer <token> (Default: empty).
Settings files created by an earlier version do not contain the RATE_LIMIT_BACKOFF_MINUTES
and NTFY_* keys. The defaults apply until you add them.
Command-Line Arguments
Every setting can also be provided as a command-line argument. Command-line arguments
always take precedence over .settings.json (and any secret provider overrides, see
Providing Secrets at Runtime), which is useful for
one-off overrides or wiring the script into other tooling without editing the settings
file. Run with --help to see all available options:
netcup-dyndns --help
options:
-h, --help show this help message and exit
--version show the version and repository URL, then exit
--settings-file PATH path to the settings JSON file
--cache-dir PATH directory for temporary IP and retry cache files
--show-paths show the resolved settings-file and cache-directory paths, then exit
--force Force a DNS update of all domains, ignoring the cached
IPv4/IPv6 addresses.
--api-password API_PASSWORD
Netcup API password. Overrides API_PASSWORD.
--api-key API_KEY Netcup API key. Overrides API_KEY.
--customer-id CUSTOMER_ID
Netcup customer ID. Overrides CUSTOMER_ID.
--netcup-domain NETCUP_DOMAIN
Comma-separated domain(s) to update, e.g.
'example.com,example.net'. Overrides NETCUP_DOMAIN.
--nextcloud-path NEXTCLOUD_PATH
Path to the Nextcloud installation. Overrides
NEXTCLOUD_PATH.
--trusted-proxies-pos TRUSTED_PROXIES_POS
Position in the trusted_proxies configuration to
update. Overrides TRUSTED_PROXIES_POS.
--parallel-processes PARALLEL_PROCESSES
Number of parallel DNS-update workers. Overrides
PARALLEL_PROCESSES.
--ip-mode {both,ipv4,ipv6}
Which IP types to update: 'both' (default), 'ipv4', or
'ipv6'. Overrides IP_MODE.
--disable-nextcloud-nginx, --no-disable-nextcloud-nginx
Disable the Nextcloud OCC / Nginx reload tasks (use
--no-disable-nextcloud-nginx to force-enable them).
Overrides DISABLE_NEXTCLOUD_NGINX.
--rate-limit-backoff-minutes MINUTES
Comma-separated minimum waiting times in minutes
between login attempts after netcup refused a login
because of too many logins, e.g. '10,30,60'.
Overrides RATE_LIMIT_BACKOFF_MINUTES.
--ntfy-server NTFY_SERVER
ntfy server URL for notifications. Overrides
NTFY_SERVER.
--ntfy-topic NTFY_TOPIC
ntfy topic to notify; empty disables notifications.
Overrides NTFY_TOPIC.
--ntfy-token NTFY_TOKEN
ntfy access token for protected topics. Overrides
NTFY_TOKEN.
Example, overriding just the domain for a single run without touching .settings.json:
netcup-dyndns --netcup-domain example.com,example.net
Docker Installation (alternative)
Instead of installing uv and Python locally, you can run this project as a lightweight
Docker container. The provided Docker/Dockerfile uses a multi-stage build: uv and dependency
resolution happen only in a throw-away build stage, and the final runtime image is based
on python:3.13-slim with just the resolved virtual environment and application code —
no uv, compilers, or other build tools are present in the image you actually run.
-
Create and fill in your
.settings.jsonfile (see Configuration for the available keys), then make sure it is readable by the container:chmod 644 .settings.json -
Build and start the container with Docker Compose:
docker compose -f Docker/docker-compose.yml up -d --buildThis mounts
.settings.jsonread-write into the container and persists the cached IP addresses in a named volume (dyndns-cache) across restarts. By default, the container checks for IP changes everyUPDATE_INTERVAL_SECONDS(300s/5 minutes); adjust this inDocker/docker-compose.ymlas needed.Alternatively, to trigger a single run (e.g. from a host cron job or systemd timer) instead of running continuously:
docker compose -f Docker/docker-compose.yml run --rm -e RUN_ONCE=true netcup-dyndns-updater -
Nextcloud/Nginx integration caveat:
nginx_trusted_proxies_configurationshells out tosudo,php occ, andsystemctl restart nginxon the host. These are not available inside the minimal container, so setDISABLE_NEXTCLOUD_NGINX: truein.settings.jsonwhen running via Docker unless you specifically bind-mount those host binaries into a privileged container (not recommended). Running purely as a DynDNS client works out of the box.
Providing Secrets at Runtime
As an alternative to storing credentials in plain text in .settings.json, the script
supports two optional, opt-in mechanisms to override settings at runtime. Both are
disabled unless explicitly configured via environment variables, and neither requires any
additional dependency.
1. Secret files (Docker/Kubernetes secrets convention)
Set an environment variable named <KEY>_FILE pointing at a file whose contents will be
read (and trimmed) to override the corresponding setting, e.g.:
API_PASSWORD_FILE=/run/secrets/api_password
API_KEY_FILE=/run/secrets/api_key
CUSTOMER_ID_FILE=/run/secrets/customer_id
This works with Docker Compose secrets:, Kubernetes Secret volumes, or any tool that
projects a secret's value into a file (including an OpenBAO Agent injector sidecar).
2. Direct retrieval from an OpenBAO (or Vault-compatible) server
Configure the following environment variables to have the script fetch a KV v2 secret at
startup and use its values to override matching settings keys (API_PASSWORD, API_KEY,
CUSTOMER_ID, etc.):
OPENBAO_ADDR=https://openbao.example.internal:8200
OPENBAO_TOKEN=s.xxxxxxxxxxxxxxxxxxxx # or OPENBAO_TOKEN_FILE=/run/secrets/openbao_token
OPENBAO_SECRET_PATH=secret/data/netcup-dyndns # optional, this is the default
The secret is expected at <OPENBAO_ADDR>/v1/<OPENBAO_SECRET_PATH> in the standard KV v2
response shape ({"data": {"data": {...}}}). Only keys already recognized by
.settings.json are applied; unknown keys in the secret are ignored.
If both mechanisms are configured, secret-file overrides take precedence over OpenBAO
values, since they are applied last. Neither mechanism is required — you can continue to
configure everything directly in .settings.json. Command-line arguments (see
Command-Line Arguments) are applied after both, and therefore
always take precedence over secret provider values as well.
Login Limits and ntfy Notifications
The netcup API blocks logins for a while when there have been too many of them. The script recognizes such a block when the login
- fails with a refused connection (
Connection refused), - is answered with HTTP status
429 Too Many Requests, or - is rejected with a netcup error message about too many logins or a reached limit.
In that case, the script logs a Connection Refused error, stops logging in for the
remaining domains of the run, and waits before the next attempt. With the default
RATE_LIMIT_BACKOFF_MINUTES of [10, 30, 60]:
| Refused login | Next attempt no sooner than |
|---|---|
| 1st | 10 minutes later |
| 2nd | 30 minutes later |
| 3rd | 60 minutes later |
| 4th and later | 60 minutes later, and a ntfy notification is sent after the 4th |
Runs that start before the next attempt is due (for example from a cron job every five minutes or the Docker loop) exit immediately without contacting any API. The waiting times are minimums: the attempt happens on the first run after the waiting time has passed. Once a login succeeds again, every configured domain is updated, the backoff is reset, and, if a notification was sent, a second notification reports that logins work again. Refused logins do not count against the per-domain retry budget.
Notifications are sent only if NTFY_TOPIC is set. To use them:
- Pick a hard-to-guess topic name on ntfy.sh (or on your own ntfy server) and subscribe to it in the ntfy app.
- Set
NTFY_TOPICin.settings.json. SetNTFY_SERVERfor a self-hosted server andNTFY_TOKENif the topic requires authentication.
NTFY_TOKEN can also be provided at runtime as described in
Providing Secrets at Runtime, e.g. via NTFY_TOKEN_FILE.
The backoff state is stored in rate_limit.json in the cache directory. Running with
--force attempts a login even while the backoff is active; deleting rate_limit.json
resets the backoff completely.
Usage
To run the script periodically, set up a cron job or a systemd timer. This will ensure your IP address is regularly checked and updated.
While updating DNS records, a progress bar is shown. Its total is derived from the number of subdomains listed in NETCUP_DOMAIN (each subdomain accounts for one A and one AAAA record), and it advances by one step for every record that is successfully updated, reaching 100% once all A and AAAA records for every configured subdomain have been updated:
Updating DNS records: 100%|██████████| 4/4 records
Regular progress is logged to stdout with timestamps and log levels (INFO/WARNING). Errors are logged in red (ERROR) so failures stand out immediately.
When multiple domains or subdomains are configured, the script prints a summary at the end grouped by domain instead of a flat sequential list, e.g.:
example.com
- sub A -> 1.2.3.4
- sub AAAA -> ::1
- www A -> 1.2.3.4
example.net
- app A -> 1.2.3.4
Configuration
Upon the first execution, the script creates a settings.json file and a temp folder.
The API identifiers for Netcup (API key, password, and customer ID) must be configured in the settings.json file. In the NETCUP_DOMAIN field, list all desired domain names separated by commas (e.g., example.com, example.net). NEXTCLOUD_PATH should point to the directory where your Nextcloud instance is located. TRUSTED_PROXIES_POS specifies the position in the TrustedProxies configuration where the new IP address should be inserted.
Contributing
When bumping the version in pyproject.toml, add a matching ## [x.y.z] - YYYY-MM-DD
section to CHANGELOG.md. After the pull request is merged into main,
that section is published as the notes of the GitHub release.
To ensure proper code formatting, run the following command:
uv run --dev ruff check
To run tests:
uv run --dev pytest
To run tests with a coverage report:
uv run --dev pytest --cov=src --cov-report=term-missing
License
Licensed under the terms of GNU General Public License v3.0. See LICENSE file.
Metadata
Release files for netcup-dyndns-and-trusted-proxies-updater 1.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| netcup_dyndns_and_trusted_proxies_updater-1.4.0.tar.gz | 32.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| netcup_dyndns_and_trusted_proxies_updater-1.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 64.8 kB
Release files / netcup_dyndns_and_trusted_proxies_updater-1.4.0.tar.gz
| Download URL | netcup_dyndns_and_trusted_proxies_updater-1.4.0.tar.gz |
|---|---|
| Size | 32.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
94028df3b9fa317f4bd081426bc12d88c61b4d7c80a9e7c1850b042d62def5ce
|
|
BLAKE2b-256 checksum How to use checksums |
9486f38030a47adefea4cfcb6caeb4f4948c6ef23bcc13ca2b814edb045c4adc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.21 {"installer":{"name":"uv","version":"0.12.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / netcup_dyndns_and_trusted_proxies_updater-1.4.0-py3-none-any.whl
| Download URL | netcup_dyndns_and_trusted_proxies_updater-1.4.0-py3-none-any.whl |
|---|---|
| Size | 32.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cf0db1bec683fb5138b837bcafc788d15d998c99d4ee7faf0cc8ab7373820c5d
|
|
BLAKE2b-256 checksum How to use checksums |
82b3a01d32d8bb6c33721da56c5583d33315e9ae9faea4555ec6891b74e2de65
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.21 {"installer":{"name":"uv","version":"0.12.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|