porkbun_ddns_script/README.md
2025-11-24 14:13:09 -05:00

12 KiB
Raw Permalink Blame History

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:

    chmod +x updateDNS.sh
    
  3. Copy the .env file and configure it (see Configuration below)

Configuration

Edit the .env file with your settings:

# 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
  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:

# .env
API_KEY=pk1_xxxxx
SECRET_KEY=sk1_xxxxx
DOMAIN=example.com
MACHINE_ID=home-server

Run the script:

./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:

# .env
API_KEY=pk1_xxxxx
SECRET_KEY=sk1_xxxxx
DOMAIN=example.com
MACHINE_ID=server1

Server 2:

# .env
API_KEY=pk1_xxxxx
SECRET_KEY=sk1_xxxxx
DOMAIN=example.com
MACHINE_ID=server2

Server 3:

# .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:

    NOTES_PREFIX="ddns:"
    
  • For each machine, the script sets the notes value to:

    ddns:<MACHINE_ID>
    
  • 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:<MACHINE_ID>"

This means:

  • Manually managed records without this prefix are left untouched.
  • Multiple machines (different MACHINE_IDs) 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:<MACHINE_ID> 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:

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:

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:

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:

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):

cd /path/to/porkbun
./logs.sh

Common options:

  • Follow logs in real-time (like tail -f):

    ./logs.sh -f
    
  • Show last N lines:

    ./logs.sh -n 100
    
  • Show logs from today only:

    ./logs.sh -t
    
  • Show logs from the last hour:

    ./logs.sh -h
    
  • Show only error messages:

    ./logs.sh -e
    
  • Show service and timer status plus recent runs:

    ./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:<MACHINE_ID>). Other records in the same zone are left untouched.

Security Best Practices

Protect Your API Keys

  1. Never commit .env to version control:

    echo "*.env" >> .gitignore
    echo ".env" >> .gitignore
    
  2. Rotate your API keys regularly in the Porkbun dashboard

  3. Use restrictive file permissions:

    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:<MACHINE_ID>)

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:

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