This is going to be an overview of my setup for connecting to specific services through a proxy and DDNS combo, local top-level domain names, and how I connect to the internal home network remotely with Twingate.
This is done on Proxmox with an LXC running Ubuntu 22.04 and Docker. However, these steps will work with any Docker installation. If you want details on installing Docker and a brief overview of all the basics you need to know to get started checkout our 7 Docker Basics for Beginners.
- Apps - List of all the apps and services.
- Home Assistant - Smart home services and automation.
- Media Server - Plex, Jellyfin, *arr stack, and more.
- Server Monitoring - Graphs and Visualizations for Unriad, Proxmox, and more.
- Surveillance System - Frigate NVR Solution with Coral TPU.
- Storage - Current Storage and Backup Solution.
- Proxy Managment - NGINX Proxy Manager, DDNS with Cloudflare, Local Domains, and more.
This is done with the Docker Compose file within this repository. Do note, I made some customizations for how I specifically like to set it up. I've changed some of the external ports to access 80, 443, and the GUI for NGINX Proxy Manager as well as placing the storage within volumes. Please change these as needed or use the official compose file as seen below. Additionally, I've added the container cloudflare-dynamic-dns as my IP address changes randomly. If you don't have a dynamic IP address or don't have intention on exposing a service to the internet you can remove this container from the compose file.
services:
proxy:
image: 'jc21/nginx-proxy-manager:latest'
container_name: nginx-proxy-manager
restart: unless-stopped
network_mode: host
volumes:
- proxy-data:/data
- proxy-letsencrypt:/etc/letsencrypt
healthcheck:
test: ["CMD", "/usr/bin/check-health"]
interval: 10s
timeout: 3s
volumes:
proxy-data:
proxy-letsencrypt:
This is setup as a host network to allow localhost and local networking connections without needing to add ports for all the services to the container.
Below is a basic compose template from NGINX if you don't want to use mine.
Checkout the quick setup section in their official repo.
services:
app:
image: 'docker.io/jc21/nginx-proxy-manager:latest'
restart: unless-stopped
ports:
- '80:80'
- '81:81'
- '443:443'
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
Due note, as seen in my docker compose you'll need to either need to set the network mode to host or expose the specific ports if running on bridge mode for servers that are running on your home network from a different machine. Also, be sure to check out their Advanced Configuration documents.
proxy:
...
network_mode: bridge
ports:
- 5080:80
- 5443:443
- 5000:81
- 8096:8096 # add ports you want to expose that are not on your local server
...
- Sign up for a Cloudflare account and use it to manage your domain using this guide.
- Within Cloudflare obtain your API token. My Profile > API Tokens > Create Token > Edit Zone DNS > Include All Zones > Create Token > Save your Token. We will be using this token in the cloudflare-ddns container configuration and when we generate SSL certificates.
This is different for every router so you may need to do additional research to do this on your specific hardware. I currently use the Omada stack for networking needs. Basically, it's like Ubiquiti but cheaper (you get what you pay for).
Open the ports on your router for the 80 and 443 ports we set up in NGINX Proxy Manager. In my docker compose file I'm using the host networking mode so I'd open the ports 80 and 443 with the local IP of the machine that NGINX Proxy Manager is installed on. In my setup I needed to set the source port and destination port. See my example below.
Source Port vs. Destination Port
Source Port: This is the port on the device that is initiating the communication. For example, when your computer sends a request to a server, it uses a source port to identify itself.
Destination Port: This is the port on the device that will receive the communication. For example, when you're connecting to a web server. The destination port is fixed for the service you're trying to reach and tells the receiving device what service or application should handle the incoming data.
If using bridge mode with custom ports, for example 5080 and 5443 as shown in the example. I'd set the destination port to 5443 and the source port to 443 for https.
- Within Cloudflare use an A record to create the root domain and/or sub-domains you wish to point to specific services within your home network. For the IPv4 address we will have our DDNS container handle that. I recommend adding a random IP now (ie. 8.8.8.8) so in the next step we can verify that it will update automatically to our public IP. Be sure to keep the 'Proxy status' option enabled.
- If you need to use DDNS, edit your Docker Compose file, add your API, and domain names including subdomains you want to set up for external access. When the container runs ensure there are no errors and the public IP in Cloudflare is updated to your actual IP.
Below is the compose template for the cloudflare-dynamic-dns container. You can use it as I have it within my compose file or set it up separately.
services:
ddns:
image: favonia/cloudflare-ddns:latest
container_name: cloudflare-ddns
# network_mode: host # This bypasses network isolation and makes IPv6 easier (optional; see below)
restart: always
user: "1000:1000" # Run the updater with specific user and group IDs (in that order).
read_only: true # Make the container filesystem read-only (optional but recommended)
cap_drop: [all] # Drop all Linux capabilities (optional but recommended)
security_opt: [no-new-privileges:true] # Another protection to restrict superuser privileges (optional but recommended)
environment:
- CLOUDFLARE_API_TOKEN=KEY
- DOMAINS=example.com,jellyfin.example.com
- PROXIED=true
- IP6_PROVIDER=none
- Now head over to NGINX Proxy Manager and create your SSL certificates. You navigate to SSL Certificates > Add SSL Certifcate. Type in your domain name and then enable 'Use a DNS Challenge'. Select Cloudflare and paste in the API we saved from earlier.
- Now in NGINX Proxy Manager navigate to Hosts > Add Proxy Host. Add the domain name for the service (ie. nextcloud.example.com) and select http (this may vary on if the service is running on https locally) then add the local IP and port for the service you want forwarded to the domain.
- Depending on the service you may need to enable Websockets Support, but I always select Block Common Exploits.
- Navigate the the SSL tab and select your SSL Certificate and enable Force SSL. See known issues below.
- Depending on the service you may need to make changes to the settings in the specific service, such as allowing proxies and add some advanced configuration, for example Jellyfin requires some additional configuration.
- Too Many Redirects: Force SSL may not work with CloudFlare proxying. issue
- Disable Cloudflare Proxy on Streaming: Jellyfin, Plex and other streaming services are not allowed to use Proxy on the free plan. Doing this technically breaks their TOS and may result in your account getting banned. Just to be safe I used a subdomain for my Jellyfin instance as a separate A-Record and disabled the Cloudflare Proxy.
Within this section we will use our NGINX Proxy Manager setup and our domain registrar directly to create a proxy host scheme for local access only. This will also allow us to use letsencrypt to generate SSL certificates for our local network. This will eliminate that horrible this site is not secure message on our services! Also, we will be setting up Twingate (a channel sponsor) to enable a zero trust network for remote access to those services we don't want to expose publically.
Assign a local IP scheme in the domain registration website. The local IP you will use is the same as the machine running NGINX Proxy Manager. (ie. 10.0.0.60). You'll want to assign this to the A-Record for the main domain and create a CNAME Record as a wildcard (*) pointing to the main domain name. Due note, this may take some time, it took about 15 minutes for the record to update for me. If you're using Cloudflare make sure you disable their proxy service.
While you're on Cloudflare or the registar find your API key. You'll need this for generating SSL certificates in the DNS challenges option. Many providers are supported and you can see a full list here.
This will mirror the steps above, with some slight differences. In NGINX Proxy Manager navigate to Hosts > Add Proxy Host. Add the domain name for the service (ie. example.com) and select http (this may vary depending on if the service is running on https locally) then add the local IP and port for the service you want forwarded to that domain. If you want to test everything check below.
There is a simple container we can use to test our domain with the local IP. In the terminal run the docker command below on the same machine that is running your Proxy Manager. This is also available as docker compose in the compose.yaml file in this repository.
docker run -p 8888:80/tcp "karthequian/helloworld:latest"
Add a subdomain (hello.example.com) in proxy hosts with the IP running this helloworld container and the port 8888. Set it to http only with no SSL since we have not set that up yet.
- Navigate to example.com:8888 to test if the A-Record and CNAME is working properly.
- Navigate to hello.example.com to test if the reverse proxy is working.
Navigate to SSL Certificates > Add SSL Certifcate. Type in your root domain name (example.com) click add then input the wildcare domain (*.example.com) and then enable 'Use a DNS Challenge'. Select your registar and paste in the API we saved from eariler. If you run into error make sure that your API key is correct, whitelist your public IP with you registar if needed, or try increasing the Propagation Seconds to 120 seconds.
With the helloworld container still running, head over to Proxy Hosts and edit the hello.example.com host. In the SSL tab add *.example.com under the SSL Certificate and enable Force SSL. Navigate to hello.example.com to ensure that the connection is automatically redirected to https.
- Namecheap API Whitelist: Namecheap isn't really the best for this if you have a Dynamic IP. Whenever I want to update my certificates I need to whitelist my public IP so it can use their API. I will be switching to using Cloudflare for this going forward.
Notice: Twingate is a channel sponsor, thus this is a bias disclosure. Twingate uses cloud based software for managing networks, resources, and users. Some users prefer to self-host every aspect of this, if that's you look into something like Netbird. For myself, Twingate has been awesome; easy to use and others rate it well.
After creating an account on Twingate and setting up your first network we need to set up a connector. Within my docker compose file I have the twingate-connector service ready to deploy with the entire stack. Here is what this service looks like.
services:
twingate_connector:
container_name: twingate_connector
restart: always
image: "twingate/connector:latest"
environment:
- TWINGATE_NETWORK=<TENANT NAME>
- TWINGATE_ACCESS_TOKEN=<ACCESS TOKEN>
- TWINGATE_REFRESH_TOKEN=<REFRESH TOKEN>
When you create your connector in the Twingate dashboard you'll generate some tokens. Enter them in the environmental variables and launch the stack. Verify a connection under networks in Twingate.
Next, create a new resource with the IP of your proxy manager and add the local root domain as an alias. Once created you should be able to have access to the local domain we created earlier including sub-domains. See the image before for an example.
Additional Security Steps | Twingate Guide |
---|---|
![]() |
![]() |