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
.envfile (excluded from git) - ✅ Error Handling - Comprehensive validation and error messages
- ✅ Wildcard Support - Updates both root and wildcard DNS records
Requirements
bashshellcurl- for API callsjq- for JSON parsing- Porkbun API access enabled for your domain
Installation
-
Clone or download this script to your machine
-
Make the script executable:
chmod +x updateDNS.sh -
Copy the
.envfile 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
- Log in to your Porkbun account
- Navigate to Account > API Access
- Generate API keys for your domain
- 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:
namematches the FQDN being updated (e.g.example.comor*.example.com), andtypeisA,ALIAS, orCNAME, andnotes == "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/deleteordns/createendpoints.
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
- Load Configuration - Reads API credentials and settings from
.env - Detect Public IP - Uses Porkbun's
/pingendpoint or fallback services (api.ipify.org, icanhazip.com) - Retrieve DNS Records - Gets all existing DNS records for the domain and displays them
- Check for Duplicates - Detects if multiple records exist for this machine ID
- Skip if Unchanged - If IP matches and exactly one record exists, skips update
- Clean Old Records - Deletes A/ALIAS/CNAME records with no machine ID (migration cleanup)
- Delete Own Records - Removes this machine's previous A records (by matching notes field)
- Verify Cleanup - Ensures all old records were deleted before creating new ones
- Create New Record - Adds fresh A record with current IP and machine ID in notes
- 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
-
Never commit
.envto version control:echo "*.env" >> .gitignore echo ".env" >> .gitignore -
Rotate your API keys regularly in the Porkbun dashboard
-
Use restrictive file permissions:
chmod 600 .env -
Use different API keys for development/production if possible
If You Accidentally Exposed Keys
- Immediately revoke the API keys in your Porkbun account
- Generate new keys and update your
.envfile - 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"
- Log in to Porkbun
- Go to your domain settings
- Enable API access for the specific domain
- 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:
- This script: Open an issue or review the code
- Porkbun API: Contact Porkbun Support
- DNS concepts: See Porkbun DNS Documentation
Contributing
Improvements welcome! Consider adding:
- IPv6 support (AAAA records)
- Email notifications on IP change
- Systemd service file
- Docker container version
- Health check endpoint