Share Your Homelab Without Port Forwarding
Put a self-hosted app like Immich on a public web address behind a login page using Pangolin, with no open ports on your home router. Every secret in the setup comes from Infisical, not a config file.
Looking to improve your secret management processes?Talk to an expert
Share Your Homelab Without Port Forwarding: Pangolin and Infisical
What this setup does
Immich, a self-hosted Google Photos, runs on a Mac mini at home. Anyone with an account can open it at photos.infisicaldemo.com from a phone on cellular, with no VPN installed, and the home router has no open ports. Every password the setup depends on, on a cloud server and on the box at home, comes from a self-hosted Infisical instance. None of them sit in a config file, a compose file, or a .env file.
The tool that makes the app reachable is Pangolin. The tool that keeps its secrets out of files is Infisical.
Where the Tailscale build left off
This build extends the Tailscale and Infisical homelab. The homelab is three Linux virtual machines on Mac minis, called mini 1, mini 2, and mini 3. One Raspberry Pi or an old PC is enough to follow along.
Tailscale is a private mesh network of your own devices, called a tailnet. Every device can reach every other one by name over an encrypted connection, and nothing is exposed to the internet. Infisical, self-hosted on mini 1, solves secret sprawl: database passwords and API keys that used to be copied into .env files next to every stack now live in one place.
Each app gets its own Infisical project, the set of secrets for that one app. Each stack starts with infisical run, which fetches the project's secrets and injects them as environment variables at startup. Each stack logs in with its own machine identity, an account for a program instead of a person, so Nextcloud's identity can only read the Nextcloud project and Grafana's can only read Grafana's.
Tailscale tags and grants control which machines can talk to each other. The only things that can reach Infisical are the homelab boxes and personal devices.
The limitation is that every device has to be on the tailnet. That is right for servers, and wrong for family.
Why sharing a self-hosted app is hard
Immich backs up a phone's camera roll, does face recognition and search, and shares albums. It is the kind of app worth sharing, but with only Tailscale in place, a family member would have to install Tailscale, sign in, and join the tailnet. Most will not, and a smart TV cannot.
The two usual workarounds both fall short here.
Port forwarding has the home router send internet traffic on a port straight to a box in the house. It puts your home IP address and the app directly on the internet. Many providers block it outright with carrier-grade NAT (CGNAT), which shares one public IP address across many customers, so there is no port to open.
A tunnel service like Cloudflare Tunnel runs a small program at home that connects out to Cloudflare, which gives the app a public address. No ports open, and it is free. But Cloudflare's terms do not allow serving video through it, uploads are capped at 100 MB, and every request passes through Cloudflare's servers. Immich is mostly photos and video.
The goal: family opens Immich in a browser with nothing to install, the home network has no open ports, and nobody reaches Immich without logging in first.
How Pangolin works
Pangolin is an open-source, self-hostable reverse proxy with authentication in front of every app. It gives an app at home a public web address, shows visitors a login page first, and only forwards the request once they are signed in. The public end runs on a server you rent and control, so no third party's terms decide what you can share.
Getting a request from a phone to Immich takes five pieces:
- A domain, a name you own, bought from a registrar for around twelve dollars a year. This build uses infisicaldemo.com from Cloudflare.
- A VPS, a small rented Linux server with its own public IP address, for a few dollars a month. This build uses DigitalOcean.
- DNS records that point photos.infisicaldemo.com, and every other name under the domain, at the VPS.
- A reverse proxy that receives web requests and forwards each one to the right app based on the hostname. Pangolin is that proxy, running on the VPS, and it checks for a login before forwarding anything.
- A tunnel, an encrypted connection from the house to the VPS. Pangolin's tunnels use WireGuard, the same protocol under Tailscale.
The Pangolin CLI runs on a machine at home, connects out to the VPS, and holds the tunnel open. Because the connection starts from inside the house, the router needs no open ports, and it works behind CGNAT.
A single request runs like this. The phone asks for photos.infisicaldemo.com, and DNS sends it to the VPS. Pangolin sees no session and shows the login page. After sign-in, Pangolin sends the request down the tunnel to the Pangolin CLI at home, which hands it to Immich.
In Pangolin's terms, the house is a site, any place the Pangolin CLI runs, and Immich is a resource, one app shared from a site. Pangolin also offers a hosted cloud version with a free plan. This build self-hosts it.
Tailscale vs Pangolin: why keep both
Both use WireGuard and both reach the homelab, but they serve different people. Tailscale is for you and your servers. Pangolin is for everyone else.
- Who it's for: Tailscale is for you and your servers. Pangolin is for the people you share an app with.
- What users install: Tailscale needs its app and a tailnet login. Pangolin needs only a browser.
- What's on the public internet: with Tailscale, nothing. With Pangolin, the VPS and its login page.
- Cost for this setup: Tailscale is free. Pangolin needs a VPS and a domain.
Pangolin puts a login page on the internet, which is more exposure than Tailscale. In exchange, people can use the app without joining your network, the home router stays closed, and your home IP address stays private. In this build, Tailscale stays the private network, Pangolin is the front door for apps you choose to share, and the VPS joins the tailnet only so it can reach Infisical.
The architecture and the secrets it creates
The VPS runs Pangolin. Mini 1 runs Infisical. Mini 3 runs Immich and the Pangolin CLI. The VPS and the minis share the tailnet, and the phone stays outside it.
The setup adds new secrets:
- Pangolin's server secret, on the VPS, the key Pangolin uses to encrypt the sensitive data it stores.
- The site ID and site secret, on mini 3, which the Pangolin CLI uses to connect to the VPS as the home site.
- Immich's database password, on mini 3.
By default these land in files. Pangolin's installer writes the server secret into a config file on the one machine that faces the internet. The site credentials and database password go into a compose file or the .env beside it. Those files get backed up, copied to new machines, and sometimes committed to git, and every copy is another place to leak from and another place to update on rotation.
The stakes are specific. Whoever reads the VPS config file holds the key to Pangolin's encrypted data. Whoever holds the site ID and secret can connect to Pangolin as the home site and receive traffic meant for the house. With every value in Infisical, none of these files contain a secret.
Get a VPS, a domain, and DNS
Buy a domain from any registrar. Then rent a VPS with at least one CPU and 2 GB of memory, running Ubuntu, in the closest region. Pangolin's guide to choosing a VPS covers the requirements.
Because the VPS is public, give it a firewall that blocks everything except:
- Ports 80 and 443 for web traffic, and so Let's Encrypt can verify the domain and issue a free HTTPS certificate.
- Port 51820 for the WireGuard tunnel from home.
- Port 22 for SSH.
These ports are open on the VPS, not on the home router.
Next, point the domain at the VPS. In the registrar's DNS settings, add an A record with the name @ and the VPS's public IP, so infisicaldemo.com resolves to it. Add a second A record with the name *, so every subdomain resolves there too and new apps never need their own record. On Cloudflare, turn the proxy switch off on both so traffic goes straight to the server.
Last, install Tailscale on the VPS with the install script, run sudo tailscale up, approve the machine from the link it prints, and rename it vps.
Lock the VPS to Infisical only
The tailnet policy file already tags mini 1 infisical and minis 2 and 3 homelab, with grants letting homelab and personal devices reach infisical on port 443. Once a tailnet has grants, any connection a grant does not allow is blocked.
Add a vps tag, one grant letting tag:vps reach tag:infisical on port 443, and apply the tag to the VPS. If someone takes over the one public machine, the only thing on the tailnet they can reach is Infisical on 443. Mini 2, mini 3, and the laptop are out of reach. A request from the VPS to Infisical goes through, and a request to mini 3 does not.
Install Pangolin with its secret in Infisical
Download and run Pangolin's quick install on the VPS with sudo. It asks for the base domain (infisicaldemo.com), the dashboard address (pangolin.infisicaldemo.com), and an email for Let's Encrypt, then sets Pangolin up with Docker. Email sending and CrowdSec, which blocks IP addresses known for attacks, are skipped here to keep the build focused.
The installer writes the server secret into Pangolin's config file in plain text. To move it, create a project called pangolin in Infisical and add SERVER_SECRET to the prod environment. Delete the value from the config file. Then, in the compose file under the Pangolin container, set SERVER_SECRET to ${SERVER_SECRET} so Compose reads it from the environment.
The VPS then needs its own way to log in. A personal account can read every project, so the VPS gets a machine identity called vps using Universal Auth, which issues a client ID and client secret. Add it to the pangolin project with the Viewer role, which can read secrets but not change them.
The VPS still holds a credential, but a narrow one: it reads only the pangolin project, it works only over the tailnet, and it can be revoked in Infisical without touching Pangolin.
Install the Infisical CLI with the apt setup from the docs. By default it talks to Infisical Cloud, so export INFISICAL_DOMAIN set to the instance's tailnet address, https://mini1.your-tailnet.ts.net. The VPS can reach that address because of the grant above.
Log in as the vps identity with infisical login, using the universal-auth method and the identity's client ID and client secret. The login returns an access token, a temporary pass that proves who the box is. Export it as INFISICAL_TOKEN, where the CLI looks for it.
Then start Pangolin with infisical run --projectId=<pangolin-project-id> --env=prod -- docker compose up -d. The project ID is on the pangolin project's settings page.
infisical run fetches SERVER_SECRET from the pangolin project's prod environment, sets it as an environment variable, and runs docker compose up. Compose passes it into the Pangolin container. The dashboard comes up at pangolin.infisicaldemo.com with a valid certificate, ready for the admin account.
Run Immich on mini 3
Immich's Docker Compose install ships a .env file whose database password defaults to postgres. Generate a random password, store it as DB_PASSWORD in a new immich project in Infisical, and remove the line from .env. What remains, the photo storage path and the Immich version, is not secret.
The database service already reads ${DB_PASSWORD}. Add the same reference to the Immich server service so both get it from the environment.
Create an immich machine identity with Universal Auth and give it Viewer on the immich project. Mini 3 also runs Grafana under its own identity, so neither stack can read the other's secrets. On mini 3, set INFISICAL_DOMAIN, log in as the immich identity, and start the stack with infisical run against the immich project. Immich is running, reachable only from inside the house.
Connect the home site and publish Immich
In the Pangolin dashboard, create a site called home. Pangolin returns a site ID, a site secret, and the endpoint of the Pangolin server. Store all three in a separate Infisical project, home-site, as SITE_ID, SITE_SECRET, and PANGOLIN_ENDPOINT. A separate project keeps the site credentials out of Immich's project, since any future app shared from the house goes through the same site.
Create a home-site machine identity with Viewer on that project. The Pangolin CLI's compose file is one container whose three settings reference those variables. Because mini 3's CLI is still logged in as immich, log in again as home-site, then start the container with infisical run against the home-site project. The home site comes online in Pangolin, connected from inside the house.
To publish Immich, add a resource called photos on the home site at photos.infisicaldemo.com, targeting mini 3 on port 2283. Pangolin issues the HTTPS certificate on its own. Authentication is on by default, so create a user for each family member. Pangolin rules can also allow or block by country. This build allows only the United States.
From a phone on cellular with Tailscale off, photos.infisicaldemo.com shows the login page, and after sign-in, Immich.
What you end up with
A VPS runs Pangolin and is the only part of the setup on the public internet. The Pangolin CLI on mini 3 connects out to it, so Immich is reachable behind a login page while the home router has nothing open. Tailscale connects the VPS and the minis privately, and the VPS can reach only Infisical. Infisical on mini 1 holds every secret, the server secret, the site credentials, and Immich's database password, and hands each one only to the stack that needs it.
To run the Infisical side yourself, start with self-hosting Infisical.
Starting with Infisical is simple, fast, and free.