LGM-OS
documentation

Your own domain, a certificate and a reverse proxy.

Publishing the NAS on the internet (external access)

This guide turns your NAS into your cloud: a name of your own (myhouse.duckdns.org), a valid certificate and access from anywhere without browser warnings. As a side effect, the interface can be installed as an app (PWA): with a self-signed certificate the browser limits the service worker, and with a valid one it does not.

There are four steps, in this order:

  1. Open 80 and 443 on the router (and only those).
  2. A domain name that follows your IP (DDNS).
  3. Issue the Let's Encrypt certificate.
  4. Publish the panel and the apps with the reverse proxy.
Publishing the NAS is the change that adds the most attack surface in its whole life. The Security section is not an appendix: read it before opening the first port.

How it ends up wired

Internet ──► router ──► NAS · Apache ──┬──► 127.0.0.1:5000   the LGM-OS panel
             80 and 443  :443 TLS      ├──► 127.0.0.1:8096   Jellyfin
                         :80 redirects └──► 127.0.0.1:8080   Nextcloud

Apache is the only thing listening from outside: it terminates TLS with the Let's Encrypt certificate and forwards to each service over 127.0.0.1. Neither the panel's port (5000) nor the apps' ports leave the NAS.

1. Before you start

  • A fixed IP for the NAS on your network: a DHCP reservation on the router or a static IP in Control Panel → Network. If the NAS changes IP, the port forwarding stops pointing at it.
  • A real public IP (no CGNAT). Check it like this:
curl -s https://api.ipify.org; echo      # the IP the internet sees you with

Compare that value with the WAN IP your router shows. If they do not match, or if the router's one is in the range 100.64.0.0100.127.255.255, you are behind CGNAT: port forwarding will not work whatever you do. Ask your ISP for a public IP (it is usually free and asking is enough) or use a VPN/tunnel.

  • A domain name: your own, or a free one with DDNS (step 3).

2. Router ports: 80 and 443, nothing else

External portProtocolDestinationWhat for
80TCPNAS-IP:80Certificate validation (HTTP-01) and redirect to https
443TCPNAS-IP:443All the real traffic, already encrypted

Port 80 is not optional even if you do not want to serve anything unencrypted: Let's Encrypt checks the domain is yours by asking for a file over port 80, and without that check there is no certificate. What you should do is make 80 only redirect to 443.

Why port 5000 is NOT opened

5000 is the panel's port. Publishing it is a mistake even though it looks like the short way:

  • It serves the self-signed certificate, so the browser will always warn you and you will end up used to skipping certificate warnings — which is exactly what makes a man-in-the-middle attack against you viable.
  • It exposes the full administration API directly, without the headers, the redirection or the filtering the proxy applies in front.
  • With an invalid certificate the PWA is crippled and some browser features are disabled.

With the reverse proxy you reach the panel at https://myhouse.duckdns.org (port 443, with a valid certificate) and 5000 keeps listening only inside the NAS, which is where it belongs.

Do not open 445 (SMB), 2049 (NFS), 3389 or, unless you really know what you are doing, 22 (SSH) either. Those protocols are meant for the local network; SMB exposed to the internet is the door most home ransomware comes through.

The NAS firewall counts too

If you have the firewall enabled (Control Panel → Security → Firewall), by default it only lets the panel and SSH through: even if the router forwards, Apache will receive nothing. Add two rules and do not touch the source (they have to be reachable from any IP):

ActionProtocolPortDescription
Allowtcp80Let's Encrypt and redirect
Allowtcp443External https access

3. DDNS with DuckDNS, step by step

Hardly any home ISP gives you a fixed IP: it changes every few days or on every router reboot. A dynamic DNS service keeps the name pointing at the current IP. DuckDNS is free, asks for no card and works well.

  1. Go to https://www.duckdns.org and press one of the sign in with buttons (Google, GitHub, Reddit…). Do not create a new password: use the account you already have.
  2. In the add domain box type the name you want (for example myhouse) and press *add domain*. Your domain is myhouse.duckdns.org.
  3. At the top of the page you will see token: xxxxxxxx-xxxx-…. Copy it. That token is your domain's password: do not share it or leave it visible in screenshots.
  4. On the NAS: Control Panel → Remote access → External accessDynamic domain (DDNS). Pick the provider DuckDNS, type the full domain (myhouse.duckdns.org), paste the token and leave the frequency at its default (it accepts 5 to 1440 minutes; below 5 the providers start rejecting requests). Turn the switch on and save; with "Update now" you check straight away that the token works. The panel shows the last published IP and the error from the last update if there was one. The other supported providers are Cloudflare (an API token with Zone · DNS · Edit permission) and No-IP (the key from the Dynamic DNS section).
  5. Check it resolves (from the NAS, or nslookup from Windows):
getent hosts myhouse.duckdns.org     # should return your public IP
curl -s https://api.ipify.org; echo  # ...the same as this one

If you would rather not depend on the NAS for this, see whether your router has a DDNS client with DuckDNS: it updates the IP even when the NAS is off, which is more robust. The call, in case you need it in a script or to force a manual update, is:

curl -s "https://www.duckdns.org/update?domains=myhouse&token=YOUR-TOKEN&ip="
# it answers OK (updated) or KO (wrong domain or token)

An empty ip parameter means "use the IP I am calling you from".

Two useful details about DuckDNS: changes take a couple of minutes to propagate, and any subdomain (films.myhouse.duckdns.org) resolves to the same IP with nothing to configure — which is what lets you publish several apps by name (step 5).

With your own domain step 3 is equivalent: point an A record at your public IP (or use your DNS provider's DDNS) and carry on from step 4.

4. Issuing the certificate

Requirements, all three at once: the domain resolves to your public IP, port 80 reaches the NAS and Apache is running. Before issuing anything, check from outside your network (mobile data on your phone, not the home Wi-Fi) that http://myhouse.duckdns.org answers something —even Apache's default page—. If that does not work, the certificate will not either.

From the interface: Control Panel → Remote access → External accessCertificate. Type the domain, a contact email (Let's Encrypt only uses it to warn you if a certificate is about to expire without renewing), accept the terms and press Issue certificate. Once issued, Use in the panel makes the interface itself serve it: it restarts for a few seconds and comes back over https with your domain, with no warnings.

The console equivalent, if you prefer to see the inside of it:

sudo certbot --apache -d myhouse.duckdns.org -m you@email --agree-tos --no-eff-email

If you already manage the reverse proxy from the panel, use certbot certonly --apache … instead: plain --apache rewrites the vhosts to add TLS and would overwrite the ones the panel generates. With certonly only the certificate is issued and the panel keeps managing the configuration.

What you need to know afterwards:

  • The files end up in /etc/letsencrypt/live/myhouse.duckdns.org/ (fullchain.pem and privkey.pem). Do not move them: certbot replaces them on renewal.
  • They expire after 90 days and renew themselves. The package installs a timer; check it with systemctl list-timers certbot.timer and rehearse it with sudo certbot renew --dry-run.
  • Let's Encrypt rate-limits failed attempts (5 per hour and domain). If it fails, fix the cause before retrying; hammering it will leave you unable to issue for a while. For testing, use --dry-run.

If you issued the certificate by hand with certbot, the panel does not find out: it will carry on with the self-signed one on 5000 and the copy you make goes stale as soon as certbot renews. This deploy hook keeps it up to date (certbot runs it on every effective renewal):

sudo tee /etc/letsencrypt/renewal-hooks/deploy/10-lgm-os.sh >/dev/null <<'EOF'
#!/bin/sh
# certbot runs this script on every effective renewal.
D=myhouse.duckdns.org
cp "/etc/letsencrypt/live/$D/fullchain.pem" /etc/nas/tls/cert.pem
cp "/etc/letsencrypt/live/$D/privkey.pem"  /etc/nas/tls/key.pem
chgrp nas /etc/nas/tls/key.pem && chmod 640 /etc/nas/tls/key.pem
systemctl restart nas-backend
EOF
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/10-lgm-os.sh

5. Publishing apps by subdomain (reverse proxy)

The idea: one name per service, all coming in through 443 and handed out by Apache according to the name the browser asks for.

NameGoes toWhat it is
myhouse.duckdns.org127.0.0.1:5000The LGM-OS panel
films.myhouse.duckdns.org127.0.0.1:8096Jellyfin
cloud.myhouse.duckdns.org127.0.0.1:8080Nextcloud

In Control Panel → Remote access → External accessReverse proxyNew rule, one service per rule:

  1. Public domain: the full name (films.myhouse.duckdns.org).
  2. Destination host and port: usually 127.0.0.1 and the app's port. The dropdown of running containers fills them in for you; if the app publishes no port, type them by hand.
  3. Redirect HTTP to HTTPS: leave it on. Anyone typing the name without https:// still ends up on the encrypted connection.
  4. Use the managed certificate: on if that name is covered by the certificate issued in step 4.
  5. Rule active: you can leave it off and publish it later without deleting it.

On save, the panel writes the vhost and reloads Apache. WebSockets work with nothing to change (the installer leaves proxy_wstunnel enabled); that is what web terminals, live logs, Home Assistant or code-server need so they do not sit there "connecting" forever. Two conditions per name:

  • It has to resolve: with DuckDNS subdomains work on their own; with your own domain create a CNAME (or A) record for each.
  • It has to have a certificate: HTTP-01 validation issues per specific name, so issue the subdomain's certificate too (or add it to the existing one with another -d). A wildcard certificate *.mydomain requires DNS-01 validation, which this guide does not cover.

And a rule worth repeating: the apps' ports are not opened on the router. The proxy reaches them over 127.0.0.1. Publishing through the proxy and also opening the port directly is leaving the back door open next to the good one.

6. Security (read this before touching the router)

While the NAS was only on your network, a misconfiguration was seen by your family. From the moment you publish it, an automated scanner sees it within hours: that is not an exaggeration, bots try /login on any IP with 443 open. This is the minimum:

  • Turn 2FA on now: Control Panel → Security → Account and 2FA. Without a second factor, the only thing between the internet and all your data is one password.
  • Run the Security advisor (Control Panel → Security) and clear its warnings. It is the honest summary of how the machine stands; do not publish with critical warnings open.
  • IP blocking enabled (Security → IP blocking). The panel already limits to 10 attempts per minute and locks the account after 5 failures, but per-IP blocking is what cuts scanning off at the root.
  • Firewall on with only 80 and 443 open to the outside.
  • Long, unique passwords, especially the administrator's. For people who only need to look, create users with the viewer role.
  • Update: Control Panel → Update LGM-OS (apt) and the panel update. A published NAS that is not updated is a matter of time, not of luck.
  • Backups, and at least one offline. Publishing multiplies the ransomware risk, and a connected copy gets encrypted with the rest. See disaster-recovery.md.
  • Look at the Audit log and Connected users now and then: that is where you see access attempts and from which IP.
  • Publish only what you need. If what you want is to watch your films away from home, publish Jellyfin and administer the panel from home. Every published name is one more door.

And the honest comparison: the safest thing is still publishing nothing. A VPN (WireGuard) on the router gives you access to the whole NAS without exposing a single service to the internet, at the cost of having to connect the VPN on every device. If your router has one, for personal use it is the best option. Publishing with a proxy and a certificate is the right option when you need to share with people who are not going to install a VPN.

7. If something does not work

SymptomUsual causeWhat to check
The domain does not resolveDDNS has not updatedgetent hosts myhouse.duckdns.org; correct token; wait a few minutes
It resolves, but does not connect from outside (from home it does)Ports not forwarded, or CGNATTry on mobile data; check the 80/443 forwarding; compare the router IP with api.ipify.org
certbot: Timeout during connect or challenge failedPort 80 does not reach the NASForwarding of 80, the firewall rule, and whether your ISP blocks 80 (some do)
502 / 503 when opening a subdomainThe app is not up or the destination port is another onedocker ps, Package Centre, sudo tail -f /var/log/apache2/error.log
The app loads but sits there "connecting"WebSockets are missing`apache2ctl -M \grep wstunnel; if it is not there: sudo a2enmod proxy_wstunnel && sudo systemctl reload apache2`
The browser warns about the certificateYou are coming in on 5000, or that name has no certificateGo in via https://myhouse.duckdns.org (443); sudo certbot certificates
Over the VPN you reach everything except the panelThe tunnel's packet size (MTU) does not fit over the mobile networkIn the client's WireGuard app, under [Interface], set MTU = 1280 (clients created by LGM-OS already have it)

Over the VPN you see everything except the panel

It is baffling because it looks as if the NAS is protecting itself from you: the phone says "connected", ping works, you reach the other machines at home… and the panel says this site can't be reached.

It is not the firewall and it is not the licence: it is the MTU. A WireGuard tunnel with no declared MTU uses 1420 bytes, which is fine over fibre but does not fit over 4G/5G (the mobile network and CGNAT take part of the packet). Small packets get through —which is why ping and almost everything works— and full ones are lost silently. The panel is exactly what travels in full packets: the TLS handshake with its certificate and then the whole interface.

The clients LGM-OS creates already come with MTU = 1280, the minimum the internet guarantees. If your client predates that version, there is no need to redo it: edit the connection in the WireGuard app and add that line under [Interface].

Diagnostic commands:

sudo apachectl configtest              # is the Apache configuration valid?
sudo systemctl status apache2
sudo journalctl -u apache2 -n 50
sudo tail -f /var/log/apache2/error.log
sudo certbot certificates              # issued certificates and their expiry

The Apache modules the proxy needs (proxy, proxy_http, proxy_wstunnel, headers, ssl, rewrite) are enabled by deploy/install.sh and deploy/update.sh. To check them:

apache2ctl -M | grep -E 'proxy|headers|ssl|rewrite'