Skip to content

Proxmox UI over HTTPS

Ahmad Hadidi

Commands below use these values. Click any highlighted value in a code block to change it. Saved in this browser only.

Container

This guide

Also uses your values: Proxmox host IP, Template folder, Bridge, Gateway, CT disk storage, SSH public keys file, Cloudflare tunnel CTID, Your Access policy

The problem

Proxmox signs its own certificate, so your browser warns you every time you open the UI, and the UI sits on port 8006.

A browser trusts a certificate without asking only when a public CA, like Let’s Encrypt, signed it. A public CA only signs a name it can check you own: a domain, not an IP or a .lan name.

This guide gets a Let’s Encrypt certificate for a name in your domain, keeps that name only on your router, and puts nginx in front of Proxmox on port 443. Only getting and renewing the certificate needs the internet; opening the UI works with the internet down.

Diagram
sequenceDiagram
    participant B as Browser
    participant M as MikroTik
    participant N as LAN proxy (CT 203)
    participant P as Proxmox
    participant C as Cloudflare DNS
    participant L as Let's Encrypt
    Note over N,L: Once, then about every 30 days (needs internet)
    N->>C: TXT record, with your API token
    L->>C: Checks the TXT record
    L-->>N: Certificate for ho4.axc.sh
    Note over B,P: Every visit (LAN only, works offline)
    B->>M: Where is ho4.lan? (optional)
    M-->>B: 192.168.0.203
    B->>N: http://ho4.lan
    N-->>B: 301 to https://ho4.axc.sh
    B->>M: Where is ho4.axc.sh?
    M-->>B: 192.168.0.203
    B->>N: https://ho4.axc.sh
    Note over B: Checks the certificate against the CA list already on your PC
    N->>P: https://192.168.0.9:8006

Install

Before you start

  • Do this once per Proxmox machine: each one gets its own LAN proxy. For each machine, pick its profile in My Proxmox, then set this guide’s own values (CTID, domain, LAN name, name from outside) in its value box.
  • Your own domain, with its DNS on Cloudflare.
  • A Cloudflare API token: dash.cloudflare.com → Manage account → Account API tokens → Create token. In the policy, change All Domains to your domain, then under DNS & Zones tick DNS → Edit and nothing else. Create it and copy the token; Cloudflare shows it only once.
  • The domain’s Zone ID: Domains → your domain → Overview, in the API box.
  • A name for the UI that only your router knows. Not one you also publish through a Cloudflare tunnel; for access from outside, use a second name (see below).

CT Creation

Proxmox host
{
read -rs -p "Root password for the new CT: " PW; echo
pct create \
203 \
/mnt/pve/evo/template/cache/alpine-3.24-default_20260714_amd64.tar.xz \
--hostname nginx-ho4 \
--memory 1024 \
--net0 name=eth0,bridge=vmbr0,firewall=1,gw=192.168.0.1,ip=192.168.0.203/24,hwaddr=DE:AD:DE:AD:02:03,type=veth,ip6=dhcp \
--rootfs local-lvm:2 \
--unprivileged 1 \
--ignore-unpack-errors \
--ostype alpine \
--password="$PW" \
--ssh-public-keys /root/.ssh/authorized_keys \
--start 1
unset PW
}
Proxmox host
pct enter 203

Enable DHCP Leasing - Alpine

In CT
# Alpine
touch /hdd_request_ip.sh
echo "udhcpc -i eth0 -F $(hostname) -x hostname:$(hostname) -r $(hostname -i)" >> /hdd_request_ip.sh
echo -e "#!/sbin/openrc-run\n\
name=hdd_request_ip\n\
description=\"Request IP Address From Router\"\n\
command=\"sh ./hdd_request_ip.sh\"\n\
command_background=true\n\
pidfile=\"/run/\$name.pid\"\n\
# output_log=\"/var/log/homepage/output.log\"\n\
# error_log=\"/var/log/homepage/error.log\"\n\
\ndepend() {\n\
\tneed net\n\
\tuse dns logger netmount\n\
}" >> /etc/init.d/hdd_request_ip
chmod +x /etc/init.d/hdd_request_ip
rc-update add hdd_request_ip
service hdd_request_ip start

Install nginx and acme.sh

In CT
apk add nginx acme.sh openssl
rc-update add nginx default
rc-update add crond default
rc-service nginx start
rc-service crond start

DNS record

MikroTik
/ip dns static add name=ho4.axc.sh address=192.168.0.203

Get the certificate

In CT
{
read -rs -p "Cloudflare API token: " CF_Token; echo
read -r -p "Cloudflare Zone ID: " CF_Zone_ID
export CF_Token CF_Zone_ID
acme.sh --issue --server letsencrypt --dns dns_cf -d ho4.axc.sh
unset CF_Token CF_Zone_ID
}

Install the certificate

In CT
mkdir -p /etc/nginx/ssl
acme.sh --install-cert -d ho4.axc.sh \
--key-file /etc/nginx/ssl/ho4.axc.sh.key \
--fullchain-file /etc/nginx/ssl/ho4.axc.sh.pem \
--reloadcmd "rc-service nginx reload"
acme.sh --install-cronjob

Proxy to Proxmox

In CT
cat > /etc/nginx/http.d/proxmox.conf <<'EOF'
server {
    listen 80;
    server_name ho4.axc.sh;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name ho4.axc.sh;

    ssl_certificate /etc/nginx/ssl/ho4.axc.sh.pem;
    ssl_certificate_key /etc/nginx/ssl/ho4.axc.sh.key;

    location / {
        proxy_pass https://192.168.0.9:8006;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_buffering off;
        client_max_body_size 0;
        proxy_connect_timeout 3600s;
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
        send_timeout 3600s;
    }
}
EOF
nginx -t && rc-service nginx reload

Optional: reach it at a .lan name

In CT
cat > /etc/nginx/http.d/lan-name.conf <<'EOF'
server {
    listen 80;
    server_name ho4.lan;
    return 301 https://ho4.axc.sh/;
}
EOF
nginx -t && rc-service nginx reload
MikroTik
/ip dns static add name=ho4.lan address=192.168.0.203

Optional: reach it from outside

Needs the Cloudflared tunnel, and a second name. On the node that hosts the tunnel:

Proxmox host
pct exec 102 -- add-cf-app --https prox.axc.sh 192.168.0.9 8006
pct exec 102 -- protect-cf-app prox.axc.sh "HDD"

Heart Beat

Your browser
https://ho4.axc.sh/

A white page in a phone browser: open https://ho4.axc.sh/?mobile=0 for the desktop UI.

Update

In CT
apk upgrade nginx acme.sh
rc-service nginx restart

Remove

Remove the .lan name

Only if you added it.

In CT
rm /etc/nginx/http.d/lan-name.conf
rc-service nginx reload
MikroTik
/ip dns static remove [find name=ho4.lan]

Remove it from the tunnel

Only if you added it. On the node that hosts the Cloudflared tunnel:

Proxmox host
pct exec 102 -- del-cf-app prox.axc.sh

Delete the CT

MikroTik
/ip dns static remove [find name=ho4.axc.sh]
Proxmox host
pct stop 203
pct destroy 203

Delete the API token on Cloudflare too.