You want to connect to your Home Assistant smart home automation server from anywhere in the world—whether via the mobile Companion App or a web browser—using a padlock-secured, end-to-end encrypted (HTTPS) connection. However, most residential internet service providers (ISPs) assign a dynamic WAN IP address that changes every time your router reboots, making SSL certificates and Dynamic DNS (DDNS) seem intimidating at first glance.
In this step-by-step engineering guide, we will set up a rock-solid, zero-subscription remote access architecture using your own custom domain. By the end of this tutorial, you will have:
- A custom domain address such as
https://home.exampledomain.compointing directly to your Home Assistant instance, - A 100% free, automatically renewed Let’s Encrypt SSL/TLS certificate,
- A resilient DDNS pipeline (via cPanel Dynamic DNS or Cloudflare DNS) that updates your IP record automatically whenever your ISP rotates your address,
- Two production-tested Home Assistant automations that detect power outages, recover DDNS after router reboots, monitor certificate expiration, and automatically restart Home Assistant to load renewed certificates into memory.
Why Don’t We Use DuckDNS in This Guide? While DuckDNS was the default free DDNS recommendation in older tutorials, recurring service outages, delayed IP propagation, and Let’s Encrypt validation timeouts have made it unreliable for mission-critical smart homes. Using your own domain via cPanel or Cloudflare gives you full ownership and superior uptime.
Core Networking Concepts: DDNS, SSL, and Port Forwarding
Understanding five core networking terms before touching any configuration files will make troubleshooting effortless:
- External IP (WAN IP): Your home router’s public address on the internet, assigned by your ISP. On most residential plans, this IP is dynamic and changes after power outages or ISP session resets.
- Internal IP (LAN IP): The private address (such as
192.168.1.50) assigned by your router to devices inside your home network, such as your headless Raspberry Pi server. - DDNS (Dynamic DNS): A mechanism that keeps your domain name (e.g.,
home.exampledomain.com) pointing to your current External IP. Home Assistant periodically sends a lightweight HTTP request to your DNS provider to update the A record whenever your WAN IP changes. - SSL / HTTPS Certificate: The cryptographic certificate behind the browser padlock icon. Issued free of charge by Let’s Encrypt, each certificate is valid for 90 days and is automatically renewed by the add-on once fewer than 30 days remain.
- Port Forwarding: A router NAT rule that routes incoming traffic arriving at a specific external port (such as TCP port 443) to the internal IP and port of your Home Assistant host (
192.168.1.50:8123).
End-to-End Connection Architecture
[Mobile Companion App / External Network]
│ Request: https://home.exampledomain.com (TCP Port 443)
▼
[DNS Resolver: home.exampledomain.com ──► 85.x.x.x] ◄── (Updated via DDNS)
│
▼
[Home Router (WAN IP: 85.x.x.x)]
│ Port Forward: External 443 ──► Internal 192.168.1.50:8123 (HTTPS UI)
│ Port Forward: External 80 ──► Internal 192.168.1.50:80 (Let's Encrypt HTTP-01)
▼
[Home Assistant Server (192.168.1.50:8123)]
└── Let's Encrypt TLS Certificate (/ssl/fullchain.pem & privkey.pem)
Prerequisite Check: Are You Behind CGNAT?
Do not skip this step—whether port forwarding will work at all depends entirely on whether your ISP places your connection behind Carrier-Grade NAT (CGNAT). Under CGNAT, multiple households share a single public IPv4 address, meaning inbound connections from the internet never reach your router even if you open ports.
- Log into your router’s administration page (typically
192.168.1.1) and locate the WAN / Internet Status IPv4 address. - From a computer or phone on the same Wi-Fi network, visit api.ipify.org to see the public IP address visible to the internet.
- Compare the two addresses:
- If both IP addresses match exactly: You have a true public IPv4 address and can proceed directly with this guide.
- If your router’s WAN IP falls between
100.64.x.xand100.127.x.x, or differs from your public IP: You are behind CGNAT.
How to Bypass CGNAT: Contact your ISP to request a public dynamic or static IPv4 address. If your ISP cannot provide one, you must use a tunnel-based solution that does not require inbound port forwarding, such as Home Assistant Link (Nabu Casa) or a Cloudflare Tunnel (cloudflared).
Domain Name and DDNS Provider Options
| Provider | Cost | Update Mechanism | Best Suited For |
|---|---|---|---|
| cPanel Dynamic DNS | Included with web hosting | Calling a unique cPanel webcall URL via curl | Users who already own a cPanel-based web hosting plan |
| Cloudflare DNS | Free | Official Home Assistant Cloudflare integration or API | Users who can point their domain nameservers to Cloudflare (top free choice) |
| deSEC (desec.io) | Free (Non-profit) | Standard dynDNS HTTPS update endpoint | Privacy-focused users looking for a European non-profit DNS host |
| FreeDNS (afraid.org) | Free (Basic tier) | Direct update URL / curl | Quick setup with custom or shared domains |
| Dynu DNS | Free (Basic tier) | Update URL or REST API | Users looking for a dedicated DDNS provider |
Free Domain Name Options and Freenom Deprecation Notice
- DigitalPlat FreeDomain: Provides free domains under
.us.kg,.dpdns.org,.qzz.io,.xx.kg, and.qd.je(1 domain per account by default) with full custom nameserver support for Cloudflare. - EU.org: Operating since 1996, EU.org offers free subdomains (e.g.,
yourname.eu.org) with custom nameservers; manual verification takes a few days.
Warning on Freenom Domains: Legacy Freenom extensions (
.tk,.ml,.ga,.cf,.gq) no longer accept registrations and are explicitly blocked from DNS automation via the Cloudflare API and the Home Assistant Cloudflare integration.
Method A: Setting Up cPanel Dynamic DNS
If you already host a website on a cPanel server, you have a fast, reliable Dynamic DNS service built right into cPanel.
Creating the DDNS Subdomain in cPanel
- Log into cPanel and click Dynamic DNS under the Domains section.
- Click Create, enter a subdomain prefix such as
home, and select your root domain to formhome.exampledomain.com. - Click Create to generate your unique webcall URL:
https://exampledomain.com/cpanelwebcall/abcdefghijklmnopqrstuvwxyzabcdef
Treat this webcall URL like a password—anyone who calls it can overwrite your subdomain’s IP address. Store it inside Home Assistant’s secrets.yaml file:
# secrets.yaml — never share this file publicly
cpanel_ddns_komut: "curl -s https://exampledomain.com/cpanelwebcall/abcdefghijklmnopqrstuvwxyzabcdef"
Next, expose the command in configuration.yaml:
# configuration.yaml
shell_command:
update_ddns: !secret cpanel_ddns_komut
Validate your configuration under Developer Tools > YAML and restart Home Assistant. You can test the service immediately under Developer Tools > Actions by calling shell_command.update_ddns.
Method B: Free DDNS with Cloudflare
If you don’t use cPanel, Home Assistant’s official Cloudflare integration provides native DDNS updates—and unlocks port-free DNS-01 certificate validation.
- Add your domain to a free Cloudflare account and update your domain registrar’s nameservers to the pair assigned by Cloudflare.
- Under the Cloudflare DNS tab, create an A record with Name
home, your current public IPv4 address, and Proxy status set to DNS only (Grey Cloud). Leaving the orange proxy cloud enabled can interfere with custom ports and cause SSL redirect loops (ERR_TOO_MANY_REDIRECTS). - Navigate to My Profile > API Tokens > Create Token, choose the Edit zone DNS template, and grant both
Zone:Zone:ReadandZone:DNS:Editpermissions for your specific zone. - In Home Assistant, go to Settings > Devices & Services > Add Integration > Cloudflare, paste your API token, and select your zone and the
homeA record.
Router Port Forwarding Configuration
First, assign a Static DHCP Lease to your Home Assistant device on your router (or set a static IP under Settings > System > Network in Home Assistant) so its LAN IP (e.g., 192.168.1.50) never changes. Then configure the following port forwarding rules on your router:
| External Port (WAN) | Internal IP (LAN) | Internal Port (LAN) | Protocol | Purpose | Requirement |
|---|---|---|---|---|---|
| 80 | 192.168.1.50 | 80 | TCP | Let’s Encrypt HTTP-01 challenge during initial issuance and every 60–90 day renewal. | MANDATORY for HTTP challenge (Method A). Not required for Cloudflare DNS challenge. |
| 443 | 192.168.1.50 | 8123 | TCP | Clean HTTPS remote access via https://home.exampledomain.com without appending a port number. | Strongly recommended for remote access. |
| (Alternative) 8123 | 192.168.1.50 | 8123 | TCP | Access via https://home.exampledomain.com:8123 if port 443 is not forwarded. | Only if external port 443 is unavailable. |
Crucial Security & Reliability Rules: Never delete the port 80 forwarding rule if you use HTTP-01 validation—Let’s Encrypt needs it every 60–90 days to renew your certificate, and the port remains closed whenever the add-on is not actively renewing. Disable your router’s own Remote Web Management on ports 80/443, and never expose internal ports such as SSH (22) or MQTT (1883) used by your ESP32 smart home sensors to the public internet.
Installing and Configuring the Let’s Encrypt Add-on
Install the official Let’s Encrypt add-on from Settings > Add-ons > Add-on Store. Open the Configuration tab, switch to Edit in YAML, and use the configuration matching your DDNS method:
Option 1: HTTP Challenge Configuration (cPanel & Standard DDNS)
domains:
- home.exampledomain.com
email: yourname@example.com
keyfile: privkey.pem
certfile: fullchain.pem
challenge: http
Option 2: DNS Challenge Configuration (Cloudflare – No Port 80 Needed)
domains:
- home.exampledomain.com
email: yourname@example.com
keyfile: privkey.pem
certfile: fullchain.pem
challenge: dns
dns:
provider: dns-cloudflare
cloudflare_api_token: YOUR_CLOUDFLARE_API_TOKEN
Click Start on the add-on’s Info tab and inspect the Log tab. Once you see Successfully received certificate, the add-on will stop on its own—this is expected one-shot behavior.
Enabling HTTPS in Home Assistant
Add the following http: block to your configuration.yaml file to activate TLS encryption and brute-force IP banning:
http:
ssl_certificate: /ssl/fullchain.pem
ssl_key: /ssl/privkey.pem
ip_ban_enabled: true
login_attempts_threshold: 5
Restart Home Assistant and set your External URL under Settings > System > Network (and inside the Companion App) to https://home.exampledomain.com. Note that plain HTTP (`http://192.168.1.50:8123`) is now disabled, and accessing `https://192.168.1.50:8123` by raw IP will trigger a browser warning because the certificate is bound to your domain name.
Automation 1: Power Outage Detection and DDNS Recovery
After a power outage, your router typically receives a new WAN IP, and your Home Assistant server often finishes booting before the router establishes its ISP link. Create a Date & Time helper named input_datetime.son_nabiz (Last Heartbeat) and add this external IP sensor to configuration.yaml:
sensor:
- platform: rest
name: Dis IP Adresi
resource: https://api.ipify.org?format=json
value_template: "{{ value_json.ip }}"
scan_interval: 600
Then add the following automation, which records a 1-minute heartbeat, waits 1 minute after a power restoration (>120s downtime) for the modem to come online, updates DDNS, notifies your phone with the new IP, and triggers Let’s Encrypt:
alias: "Elektrik Kesintisi Takibi ve DDNS"
description: >-
Records a 1-minute heartbeat. On startup after >2 minutes of downtime,
waits for the router, updates DDNS, sends a mobile notification with the
current WAN IP, and starts the Let's Encrypt add-on.
triggers:
- trigger: time_pattern
minutes: /1
id: nabiz_guncelle
- trigger: homeassistant
event: start
id: sistem_baslangic
- trigger: time_pattern
minutes: /10
id: dns_guncelle
- trigger: event
event_type: elektrik_kesintisi_test
id: elektrik_kesintisi_test
conditions: []
actions:
- choose:
- conditions:
- condition: trigger
id: nabiz_guncelle
sequence:
- action: input_datetime.set_datetime
target:
entity_id: input_datetime.son_nabiz
data:
timestamp: "{{ now().timestamp() }}"
- conditions:
- condition: trigger
id: sistem_baslangic
- condition: template
value_template: >
{{ states('input_datetime.son_nabiz') not in
['unknown', 'unavailable', 'none', ''] }}
sequence:
- variables:
kesinti_saniye: >
{{ (now().timestamp() -
as_timestamp(states('input_datetime.son_nabiz'), 0)) | int }}
kesinti_dakika: >
{{ ((now().timestamp() -
as_timestamp(states('input_datetime.son_nabiz'), 0)) / 60)
| round(1) }}
- condition: template
value_template: "{{ kesinti_saniye > 120 }}"
- action: input_datetime.set_datetime
target:
entity_id: input_datetime.son_nabiz
data:
timestamp: "{{ now().timestamp() }}"
- delay:
minutes: 1
- action: shell_command.update_ddns
data: {}
- variables:
dis_ip: >
{% set ip = states('sensor.dis_ip_adresi') %}
{% if ip not in ['unknown', 'unavailable', 'none', ''] %}{{ ip }}{% else %}Unknown{% endif %}
- action: notify.mobile_app_telefonum
data:
title: "Power Outage Recovery"
message: >
Reboot detected after ~{{ kesinti_dakika }} minutes of downtime.
Current WAN IP: {{ dis_ip }}. DDNS updated and SSL check started.
- action: hassio.addon_start
data:
addon: core_letsencrypt
- conditions:
- condition: trigger
id: elektrik_kesintisi_test
sequence:
- variables:
test_kesinti_saniye: 900
test_kesinti_dakika: "{{ (900 / 60) | round(1) }}"
dis_ip: >
{% set ip = states('sensor.dis_ip_adresi') %}
{% if ip not in ['unknown', 'unavailable', 'none', ''] %}{{ ip }}{% else %}Unknown{% endif %}
- action: notify.mobile_app_telefonum
data:
title: "TEST - Power Outage Alert"
message: >
Simulated outage: {{ test_kesinti_dakika }} min ({{ test_kesinti_saniye }}s).
Current WAN IP: {{ dis_ip }}.
- conditions:
- condition: trigger
id: dns_guncelle
sequence:
- action: shell_command.update_ddns
data: {}
mode: single
max_exceeded: silent
Automation 2: Daily SSL Renewal Check and Automatic Restart
Even after the Let’s Encrypt add-on renews your certificate files on disk, Home Assistant continues serving the old certificate cached in RAM until the core service is restarted. Add the Certificate Expiry integration for home.exampledomain.com and use this daily 04:00 AM automation to renew the certificate, restart Home Assistant when fewer than 29 days remain on the active certificate, and alert you if fewer than 14 days remain:
alias: "SSL: Günlük Sertifika Kontrolü"
description: >-
Runs the Let's Encrypt add-on daily at 04:00 AM, refreshes the Certificate
Expiry sensor, and restarts Home Assistant to load newly renewed certificates.
triggers:
- trigger: time
at: "04:00:00"
conditions: []
actions:
- action: hassio.addon_start
data:
addon: core_letsencrypt
- delay:
minutes: 3
- action: homeassistant.update_entity
target:
entity_id: sensor.sertifika_bitis
- delay:
seconds: 30
- variables:
kalan_gun: >
{% set bitis = states('sensor.sertifika_bitis') %}
{% if bitis in ['unknown', 'unavailable', 'none', ''] %}-1
{% else %}{{ ((as_timestamp(bitis) - now().timestamp()) / 86400) | int }}{% endif %}
- choose:
- conditions:
- condition: template
value_template: "{{ kalan_gun | int < 0 }}"
sequence:
- action: notify.mobile_app_telefonum
data:
title: "SSL Check: Certificate Unreadable"
message: >
Could not read certificate expiry date. Check your domain and port forwarding.
- conditions:
- condition: template
value_template: "{{ kalan_gun | int < 14 }}"
sequence:
- action: notify.mobile_app_telefonum
data:
title: "SSL WARNING: Renewal Failing"
message: >
Only {{ kalan_gun }} days left on SSL certificate! Check port 80 forwarding
and Let's Encrypt add-on logs.
- action: homeassistant.restart
- conditions:
- condition: template
value_template: "{{ kalan_gun | int < 29 }}"
sequence:
- action: notify.mobile_app_telefonum
data:
title: "SSL: Certificate Renewed"
message: >
Active certificate had {{ kalan_gun }} days remaining. Restarting Home Assistant
to apply the renewed certificate.
- action: homeassistant.restart
mode: single
Real-World Troubleshooting: The Port 80 Android App Lockout
Three months after an initially flawless setup, the Home Assistant Android Companion App suddenly refused to connect with the error: “SSL handshake failed, please make sure your certificate is valid.” Inspecting the Let’s Encrypt add-on logs revealed Timeout during connect (likely firewall problem).
Root Cause & Fix: Port 80 had been removed from the router’s port forwarding table after the initial issuance. When day 60 arrived, Let’s Encrypt could not reach port 80 for HTTP-01 renewal, allowing the certificate to expire. Re-adding the permanent External 80 ──► Internal 192.168.1.50:80 (TCP) rule, running the Let’s Encrypt add-on, restarting Home Assistant via browser, and clearing the Android Companion App cache immediately restored connectivity.
Common Errors, NAT Loopback, and Solutions Matrix
| Symptom / Log Message | Likely Root Cause | Resolution |
|---|---|---|
Timeout during connect in add-on log | Port 80 not forwarded, internal IP changed, or ISP CGNAT active | Verify CGNAT status and ensure TCP port 80 is forwarded to Home Assistant’s static LAN IP. |
Connection refused in add-on log | Router remote management or NGINX add-on occupying port 80 | Disable router remote web management and stop conflicting port 80 add-ons during renewal. |
| Works on cellular data, fails on home Wi-Fi | Router lacks NAT Loopback (Hairpin NAT) support | Enable NAT Loopback on your router, or configure a local DNS Rewrite in AdGuard Home / Pi-hole mapping home.exampledomain.com to 192.168.1.50 (requires external and internal ports to match, e.g., 8123). |
| Certificate renewed on disk, browser still shows expired | Home Assistant still serving old certificate from RAM | Restart Home Assistant and clear the mobile app cache. |
cp: can't create '/ssl//ssl/privkey.pem' | Full path entered instead of filename in add-on config | Set keyfile: privkey.pem and certfile: fullchain.pem without leading /ssl/ paths. |
Sources and References
This technical guide was compiled using the following official documentation and community resources:
- Home Assistant Community – How to configure Let’s Encrypt SSL certificates for Home Assistant
- Home Assistant Community – Automate Let’s Encrypt certificate renewals
- cPanel Documentation – Dynamic DNS (DDNS) Configuration
- Home Assistant Documentation – Cloudflare Integration
- Cloudflare Developers – Create API Token Guide
- DigitalPlat FreeDomain – GitHub Repository
- EU.org – Free Domain Name Service

