porkbun_ddns_script/README.md
2025-11-24 12:56:35 -05:00

10 KiB

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 for multi-machine setup: Unique identifier for this machine
# Each machine should have a different MACHINE_ID
MACHINE_ID=server1

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.

Automated Updates with Cron

To automatically update DNS every 5 minutes:

crontab -e

Add this line (adjust path as needed):

*/5 * * * * /home/user/porkbun/updateDNS.sh >> /var/log/porkbun-ddns.log 2>&1

Or every hour:

0 * * * * /home/user/porkbun/updateDNS.sh >> /var/log/porkbun-ddns.log 2>&1

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.

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. Clean Old Records - Deletes A/ALIAS/CNAME records with no machine ID (migration cleanup)
  7. Delete Own Records - Removes this machine's previous A records (by matching notes field)
  8. Verify Cleanup - Ensures all old records were deleted before creating new ones
  9. Create New Record - Adds fresh A record with current IP and machine ID in notes
  10. Verify Creation - Confirms exactly one record exists for this machine

Note: The script now 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
  3. Remove secrets from git history if they were committed:
    git filter-branch --force --index-filter \
      "git rm --cached --ignore-unmatch .env" \
      --prune-empty --tag-name-filter cat -- --all
    

Troubleshooting

Error: "dig: command not found"

The script now uses curl instead of dig, so this shouldn't happen. If you see this, update to the latest version.

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 (add to cron for automatic updates)
  • 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: KACPER_NIX
Mon Nov 24 12:04:38 PM EST 2025 [INFO] Domain: aksal.cloud

Mon Nov 24 12:04:38 PM EST 2025 [INFO] Processing DNS record for: aksal.cloud (IP: 45.73.134.145)
Mon Nov 24 12:04:38 PM EST 2025 [INFO] Machine ID: KACPER_NIX
Mon Nov 24 12:04:39 PM EST 2025 [INFO] Existing records for FQDN 'aksal.cloud':
   Type: A, Name: aksal.cloud, Content: 45.73.134.145, Notes: ddns:KACPER_NIX, Managed: true, ID: 508029248
   Type: NS, Name: aksal.cloud, Content: curitiba.porkbun.com, Notes: none, Managed: false, ID: 506591857
   Type: NS, Name: aksal.cloud, Content: fortaleza.porkbun.com, Notes: none, Managed: false, ID: 506591856
   Type: NS, Name: aksal.cloud, Content: maceio.porkbun.com, Notes: none, Managed: false, ID: 506591854
   Type: NS, Name: aksal.cloud, 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: *.aksal.cloud (IP: 45.73.134.145)
Mon Nov 24 12:04:39 PM EST 2025 [INFO] Machine ID: KACPER_NIX
Mon Nov 24 12:04:40 PM EST 2025 [INFO] Existing records for FQDN '*.aksal.cloud':
   Type: A, Name: *.aksal.cloud, Content: 45.73.134.145, Notes: ddns:KACPER_NIX, 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.

Support

For issues with:

Contributing

Improvements welcome! Consider adding:

  • IPv6 support (AAAA records)
  • Email notifications on IP change
  • Systemd service file
  • Docker container version
  • Health check endpoint