# Porkbun Dynamic DNS Update Script A bash script for automatically updating DNS A records on Porkbun with your current public IP address. Supports multi-machine failover configurations where multiple servers can independently update their own A records for the same domain. ## Features - ✅ **Automatic IP Detection** - Uses Porkbun's API and multiple fallback methods - ✅ **Multi-Machine Failover** - Multiple machines can maintain A records for the same domain - ✅ **Machine Identification** - Uses notes field to track which machine owns each record - ✅ **Secure** - API credentials stored in `.env` file (excluded from git) - ✅ **Error Handling** - Comprehensive validation and error messages - ✅ **Wildcard Support** - Updates both root and wildcard DNS records ## Requirements - `bash` shell - `curl` - for API calls - `jq` - for JSON parsing - Porkbun API access enabled for your domain ## Installation 1. Clone or download this script to your machine 2. Make the script executable: ```bash chmod +x updateDNS.sh ``` 3. Copy the `.env` file and configure it (see Configuration below) ## Configuration Edit the `.env` file with your settings: ```bash # Required: Your Porkbun API credentials API_KEY=pk1_your_api_key_here SECRET_KEY=sk1_your_secret_key_here # Required: The domain to update DOMAIN=example.com # Required: Name of the machine for tracking MACHINE_ID="exmample_machine" ``` ### Getting Porkbun API Credentials 1. Log in to your [Porkbun account](https://porkbun.com/account/api) 2. Navigate to **Account > API Access** 3. Generate API keys for your domain 4. **Important**: Enable API access for your specific domain in the domain settings ## Usage ### Single Machine (Simple Dynamic DNS) Basic configuration on one machine: ```bash # .env API_KEY=pk1_xxxxx SECRET_KEY=sk1_xxxxx DOMAIN=example.com MACHINE_ID=home-server ``` Run the script: ```bash ./updateDNS.sh ``` This will update: - `example.com` → Your current public IP - `*.example.com` → Your current public IP ### Multi-Machine Failover Setup Configure multiple machines with **the same domain** but **different MACHINE_IDs**: **Server 1:** ```bash # .env API_KEY=pk1_xxxxx SECRET_KEY=sk1_xxxxx DOMAIN=example.com MACHINE_ID=server1 ``` **Server 2:** ```bash # .env API_KEY=pk1_xxxxx SECRET_KEY=sk1_xxxxx DOMAIN=example.com MACHINE_ID=server2 ``` **Server 3:** ```bash # .env API_KEY=pk1_xxxxx SECRET_KEY=sk1_xxxxx DOMAIN=example.com MACHINE_ID=backup-server ``` Each machine runs the same script independently. The result will be: ``` example.com. 300 IN A 203.0.113.10 (server1's IP) example.com. 300 IN A 198.51.100.20 (server2's IP) example.com. 300 IN A 192.0.2.30 (backup-server's IP) ``` DNS clients will receive all three IPs, providing: - **Automatic failover** - if one server is down, clients use others - **Load distribution** - traffic is distributed across servers - **Independent updates** - each machine manages only its own record ### Managed records and notes tagging The script only manages records that it created itself, identified via the DNS **notes** field. - Internally it uses a managed-notes prefix: ```bash NOTES_PREFIX="ddns:" ``` - For each machine, the script sets the notes value to: ```text ddns: ``` - When cleaning up or checking for duplicates, it **only** looks at records where: - `name` matches the FQDN being updated (e.g. `example.com` or `*.example.com`), and - `type` is `A`, `ALIAS`, or `CNAME`, and - `notes == "ddns:"` This means: - Manually managed records without this prefix are left untouched. - Multiple machines (different `MACHINE_ID`s) can safely coexist on the same domain. - If you ever want to manually take control of a record that was created by this script, you can remove or change the `ddns:` note and the script will stop managing it. ## Dry-run mode You can run the script in **dry-run** mode to see what it would do without actually creating or deleting any DNS records. Set the `DRY_RUN` environment variable to `1` when invoking the script: ```bash DRY_RUN=1 ./updateDNS.sh ``` In dry-run mode: - The script still loads configuration and detects your public IP. - It still retrieves and displays existing DNS records. - It logs which records **would** be deleted and what **would** be created. - It does **not** call Porkbun's `dns/delete` or `dns/create` endpoints. This is useful when first configuring the script or making changes to your DNS setup, so you can verify behavior before applying it. ## Systemd integration helpers This repository also includes helper scripts to install the updater as a systemd service, uninstall it cleanly, and view logs more easily. ### `install.sh` – install systemd timer + service `install.sh` sets up a `porkbun-ddns` systemd service and timer that will run `updateDNS.sh` for you on a schedule (default: every 5 minutes). What it does: - Verifies required files exist in the current directory: - `updateDNS.sh` - `.env` - `porkbun-ddns.service` - `porkbun-ddns.timer` - Ensures `updateDNS.sh` is executable. - Checks for required dependencies: `curl`, `jq`, `systemctl`. - Validates that `.env` contains `API_KEY`, `SECRET_KEY`, `DOMAIN`, and `MACHINE_ID`. - Runs `updateDNS.sh` once as a test (with your current config). - Installs and enables: - `/etc/systemd/system/porkbun-ddns.service` - `/etc/systemd/system/porkbun-ddns.timer` - Starts the timer and shows its status. Usage: ```bash cd /path/to/porkbun ./install.sh ``` Notes: - Do **not** run `install.sh` directly as root; it will use `sudo` when needed. - The service runs as the current user (it rewrites `User=` and `Group=` in the service file to match who ran `install.sh`). After installation, some useful commands: ```bash systemctl status porkbun-ddns.timer systemctl status porkbun-ddns.service journalctl -u porkbun-ddns.service -n 50 ``` ### `uninstall.sh` – remove systemd timer + service `uninstall.sh` cleanly removes the `porkbun-ddns` systemd units while leaving your scripts and configuration files intact. What it does: - Stops `porkbun-ddns.timer` and `porkbun-ddns.service` if they are running. - Disables the timer so it no longer starts at boot. - Removes `/etc/systemd/system/porkbun-ddns.service` and `/etc/systemd/system/porkbun-ddns.timer`. - Reloads systemd and resets any failed state for these units. Usage: ```bash cd /path/to/porkbun ./uninstall.sh ``` Notes: - Do **not** run `uninstall.sh` as root; it will use `sudo` for systemd operations. - Your local files remain: - `updateDNS.sh` - `.env` - `porkbun-ddns.service` - `porkbun-ddns.timer` so you can reinstall later with `./install.sh`. ### `logs.sh` – convenient log viewer `logs.sh` is a small wrapper around `journalctl` and `systemctl` to make it easier to inspect the `porkbun-ddns.service` logs and status. Basic usage (show last 50 lines): ```bash cd /path/to/porkbun ./logs.sh ``` Common options: - Follow logs in real-time (like `tail -f`): ```bash ./logs.sh -f ``` - Show last N lines: ```bash ./logs.sh -n 100 ``` - Show logs from today only: ```bash ./logs.sh -t ``` - Show logs from the last hour: ```bash ./logs.sh -h ``` - Show only error messages: ```bash ./logs.sh -e ``` - Show service and timer status plus recent runs: ```bash ./logs.sh -s ``` Run `./logs.sh --help` for the full list of options. ## How It Works 1. **Load Configuration** - Reads API credentials and settings from `.env` 2. **Detect Public IP** - Uses Porkbun's `/ping` endpoint or fallback services (api.ipify.org, icanhazip.com) 3. **Retrieve DNS Records** - Gets all existing DNS records for the domain and displays them 4. **Check for Duplicates** - Detects if multiple records exist for this machine ID 5. **Skip if Unchanged** - If IP matches and exactly one record exists, skips update 6. **Delete Own Records** - Removes this machine's previous A records (by matching notes field) 7. **Verify Cleanup** - Ensures all old records were deleted before creating new ones 8. **Create New Record** - Adds fresh A record with current IP and machine ID in notes 9. **Verify Creation** - Confirms exactly one record exists for this machine > Note: The script only deletes and verifies records that it manages > (those whose notes are exactly `ddns:`). Other records in the > same zone are left untouched. ## Security Best Practices ### Protect Your API Keys 1. **Never commit `.env` to version control**: ```bash echo "*.env" >> .gitignore echo ".env" >> .gitignore ``` 2. **Rotate your API keys regularly** in the Porkbun dashboard 3. **Use restrictive file permissions**: ```bash chmod 600 .env ``` 4. **Use different API keys** for development/production if possible ### If You Accidentally Exposed Keys 1. **Immediately revoke** the API keys in your Porkbun account 2. **Generate new keys** and update your `.env` file ## Troubleshooting ### Error: "API access not enabled" 1. Log in to Porkbun 2. Go to your domain settings 3. Enable API access for the specific domain 4. Wait a few minutes for changes to propagate ### Error: "Failed to create DNS record" - Check that API credentials are correct - Verify API access is enabled for your domain - Ensure MACHINE_ID doesn't contain special characters - Check that you're not exceeding Porkbun's rate limits ### Multiple machines overwriting each other - Make sure each machine has a **unique MACHINE_ID** - Verify each machine is using the latest version of the script - Check DNS records in Porkbun dashboard to see notes field (look for `ddns:`) ### IP not updating - Check script is running - Verify your public IP actually changed - Check Porkbun API status - Review logs for error messages ## API Rate Limits Porkbun's API has rate limits. For dynamic DNS: - **Recommended**: Run every 5-15 minutes - **Don't**: Run more than once per minute - The script only makes API calls when it runs, not continuously ## Example Output Example output with the new structured logging format: ```text Mon Nov 24 12:04:38 PM EST 2025 [INFO] Using environment file: /home/user/porkbun/.env IP source: Porkbun API (/ping) Mon Nov 24 12:04:38 PM EST 2025 [INFO] Current Public IP: 45.73.134.145 Mon Nov 24 12:04:38 PM EST 2025 [INFO] Machine ID: EXAMPLE_MACHINE Mon Nov 24 12:04:38 PM EST 2025 [INFO] Domain: example.com Mon Nov 24 12:04:38 PM EST 2025 [INFO] Processing DNS record for: example.com (IP: 45.73.134.145) Mon Nov 24 12:04:38 PM EST 2025 [INFO] Machine ID: EXAMPLE_MACHINE Mon Nov 24 12:04:39 PM EST 2025 [INFO] Existing records for FQDN 'example.com': Type: A, Name: example.com, Content: 45.73.134.145, Notes: ddns:EXAMPLE_MACHINE, Managed: true, ID: 508029248 Type: NS, Name: example.com, Content: curitiba.porkbun.com, Notes: none, Managed: false, ID: 506591857 Type: NS, Name: example.com, Content: fortaleza.porkbun.com, Notes: none, Managed: false, ID: 506591856 Type: NS, Name: example.com, Content: maceio.porkbun.com, Notes: none, Managed: false, ID: 506591854 Type: NS, Name: example.com, Content: salvador.porkbun.com, Notes: none, Managed: false, ID: 506591855 Mon Nov 24 12:04:39 PM EST 2025 [INFO] Record already exists with correct IP (45.73.134.145). No update needed. Mon Nov 24 12:04:39 PM EST 2025 [INFO] Processing DNS record for: *.example.com (IP: 45.73.134.145) Mon Nov 24 12:04:39 PM EST 2025 [INFO] Machine ID: EXAMPLE_MACHINE Mon Nov 24 12:04:40 PM EST 2025 [INFO] Existing records for FQDN '*.example.com': Type: A, Name: *.example.com, Content: 45.73.134.145, Notes: ddns:EXAMPLE_MACHINE, Managed: true, ID: 508029259 Mon Nov 24 12:04:40 PM EST 2025 [INFO] Record already exists with correct IP (45.73.134.145). No update needed. Mon Nov 24 12:04:40 PM EST 2025 [INFO] DNS update completed successfully ``` ## Files - `updateDNS.sh` - Main script - `.env` - Configuration file (contains secrets, **DO NOT COMMIT**) - `README.md` - This file ## License This script is provided as-is for use with Porkbun DNS services. ## Contributing Improvements welcome! Consider adding: - IPv6 support (AAAA records) - Email notifications on IP change - Health check endpoint