What this achieves
This setup provides access to locally hosted services (Home Assistant, Immich, Frigate, Paperless, etc.) without sacrificing security. By using an iOS Shortcut that triggers when you leave Wi-Fi, your mobile device automatically adds its current IP address to a “Safe List” in Cloudflare.
This allows you to maintain a strict “MFA-Required” policy for the open internet, while granting a “Bypass” (no login screen) to your trusted mobile devices. It essentially treats your mobile data connection as a “Known Network,” similar to how your home internet functions, making the external access experience much smoother for family members (WIFE FRIENDLY!!)
After almost 12 months of reading posts, AI bots and LOTS of trial and error… below is how I ended up doing it for me and thought others may be interested. This is not just for HA, it is for other applications. Of course I say support Nabu Casa…
1. Create your CF Safe IP List
Navigate to Zero Trust → Reusable components → Lists.
- Create manual list:
- List name:
safe-ip-list - List type: IP addresses
- Add entry: Enter one static IP you wish to keep (e.g., your home internet).
- List name:
- Gather IDs for the shortcut & script later on:
- Click the three dots on your new list → Edit.
- Check the URL bar. It should look like:
https://one.dash.cloudflare.com/ACCOUNT_ID/reusable-components/lists/LIST_ID - Save the ACCOUNT_ID and the LIST_ID.
2. Generate API Token
-
Go to the main Cloudflare Dashboard (top left).
-
Select 3 dots next to your name → Account API Tokens.
-
Create Token → Create Custom Token → Get Started.
-
Permissions:
Account→Account Filter Lists→Edit
-
Save the Bearer TOKEN.
-
Note: If you run into permission issues, you may also need:
Account→Zero Trust→EditAccount→Access: Apps and Policies→Edit
3. Create the Access Policy
Navigate to Zero Trust → Access → Policies → Reusable policies.
- Add a policy:
- Name:
IP-Exclude - Action: Bypass
- Session Duration:
24 hours
- Name:
- Add the rule - Navigate to Add rules → Add include
- Rules: Selector:
IP list - Value:
safe-ip-list
- Rules: Selector:
4. Apply Policy to Applications
Navigate to Zero Trust → Access controls → Applications.
- Edit your application (e.g., Home Assistant, Immich) – Note; If you don’t currently have an application Add an application → Self-hosted
- Under Policies, select IP-Exclude.
- Confirm.
Note: Ensure your Tunnel public hostname routes match the Application subdomain under Networks → Connectors → Tunnel → Configure.
If you don’t already have your applications created and host names linked to your local service, you need to do that as well.
Zero Trus → Networks → Connectors → 3 dots next to the tunnel → Configure → Published application routes → Then that subdomain/domain needs to match the application, and you just put the local address of your service below. Ie. HA = http → 192.168.1.10:8123
You need to repeat this process for each application (e.g., Home Assistant, Immich, etc)
It should be noted that I have two policies per application, the above ‘bypass’ as well as an ‘allow’ policy which has email addresses in the include and my country in the require. Please read up or watch videos for further info on how
5. Automate IP Updates (iOS Shortcuts)
- Import Shortcut: Cloudflare IP Update Shortcut
- Edit Shortcut: Enter your
ACCOUNT_ID,LIST_ID, andTOKEN(there are 2 of each, 6 in total to enter). - Create Automation:
- Open Shortcuts App → Automation → +.
- Search for Wi-Fi → Select Is Disconnected.
- Select Run Immediately.
- Set the action to run the shortcut you just saved.
Note; This process will only add new IP’s, not duplicates. PS. I am currently only using iOS primarily for mobile devices, so I can’t provide the Android equivalent.
6a. Housekeeping (Python Script)
To keep your IP list clean and secure, use this Python script to remove old, inactive mobile IPs.
Requirements
- Python 3.x
- Requests library:
pip install requests
Save as cloudflare-iplist-cleaner.py:
#!/usr/bin/env python3
import requests
import json
from datetime import datetime
print(f"=== Run started {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} ===", flush=True)
# --- Configuration ---
ACCOUNT_ID = "CF_ACCOUNT_ID"
LIST_ID = "YOUR_LIST_ID"
TOKEN = "YOUR_ACCESS_TOKEN"
TARGET_DESCRIPTIONS = ["Mobile phone IPv6 iOS shortcut", "Mobile phone IPv4 iOS shortcut"]
KEEP_COUNT = 4
URL = f"https://api.cloudflare.com/client/v4/accounts/{ACCOUNT_ID}/gateway/lists/{LIST_ID}"
HEADERS = {
"Authorization": f"Bearer {TOKEN}",
"Content-Type": "application/json"
}
def clean_cloudflare_list():
response = requests.get(URL, headers=HEADERS)
if not response.ok:
print(f"Failed to fetch list: {response.text}")
return
data = response.json()
items = data.get("result", {}).get("items", []) or []
to_remove_ips = [] # Changed to store just strings
for desc in TARGET_DESCRIPTIONS:
filtered_items = [i for i in items if i.get("description") == desc]
# Sort newest to oldest
filtered_items.sort(key=lambda x: x['created_at'], reverse=True)
if len(filtered_items) > KEEP_COUNT:
excess_items = filtered_items[KEEP_COUNT:]
for item in excess_items:
# FIX: Add only the string value, not a dictionary
to_remove_ips.append(item["value"])
print(f"Marked for deletion: {item['value']} ({item['description']} - {item['created_at']})")
if to_remove_ips:
# The API expects: {"remove": ["1.1.1.1", "2.2.2.2"]}
payload = {"remove": to_remove_ips}
patch_response = requests.patch(URL, headers=HEADERS, json=payload)
if patch_response.ok:
print(f"Successfully removed {len(to_remove_ips)} old entries.")
else:
print(f"Error during removal: {patch_response.text}")
else:
print("No cleanup necessary.")
if __name__ == "__main__":
clean_cloudflare_list()
Change the TARGET_DESCRIPTIONS above (Ie. Mobile phone IPv6 iOS shortcut) to suit what you called them in the shortcut description.
Give permission to execute the script
chmod +x cloudflare-iplist-cleaner.py
6b. Housekeeping (Python Script - automation/crontab)
You can run it as often as you like, here is what my crontab looks like:
0 3 * * * /usr/bin/python3 /home/spaldo/cloudflare-iplist-cleaner.py >> /home/spaldo/cf_cleaner.log 2>&1
Disclaimer; I am not a security expert and this may not suit you if you are after super security…
Thanks to @Lewis and @final_blueberry in this forum who also worked on similar solutions.