TOO_MANY_REDIRECTS when reverse_proxy to PVE that runs Caddy

1. The problem I’m having:

Hi. Have been running Caddy for years for servers like HomeAssistant without issues, but not come across this scenario before and not sure how to fix. I recently moved to Proxmox to host my servers - I have Caddy running here in a LXC. I want to access my Proxmox VE over SSL from outside my network. I have a very simple Caddyfile which works perfectly for about 4 other servers but when I follow the same process for my PVE’s IP address, my browser shows a TOO_MANY_REDIRECTS error. I’m a novice and can’t understand why it is getting tripped up - I guess it has something to do with Proxmox already using it’s own self-signed certificate (which most browsers mark as insecure).

2. Error messages and/or full log output:

This page isn’t working
pve-01.mydomain.com redirected you too many times.

Try deleting your cookies.
ERR_TOO_MANY_REDIRECTS

3. Caddy version:

v2.10.0

4. How I installed and ran Caddy:

a. System environment:

Proxmox LXC running on Debian. AMD64

b. Command:

I used a community script

c. Service/unit/compose file:

systemd

d. My complete Caddy config:

homeassistant.mydomain.com {
        reverse_proxy 192.168.1.9:8123
}

tasmoadmin.mydomain.com {
        reverse_proxy 192.168.1.9:9541
}

blueiris.mydomain.com {
        reverse_proxy 192.168.1.3:3369
}

pve-01.mydomain.com {
        reverse_proxy 192.168.1.2:8006
}

My Proxmox VE is running on 192.168.1.2
Caddy server is running on 192.168.1.6

5. Links to relevant resources:

Run the following command and share its output:

curl -Lvk --max-redirs 3 URL_YOU_HAVE_PROBLEM_WITH

Hey. Thanks so much for your interest and help…

root@caddy:~# curl -Lvk --max-redirs 3 pve-01.mydomain.com
*   Trying 111.222.60.148:80...
* Connected to pve-01.mydomain.com (aaa.bbb.60.148) port 80 (#0)
> GET / HTTP/1.1
> Host: pve-01.mydomain.com
> User-Agent: curl/7.88.1
> Accept: */*
> 
< HTTP/1.1 308 Permanent Redirect
< Connection: close
< Location: https://pve-01.mydomain.com/
< Server: Caddy
< Date: Sun, 29 Jun 2025 04:04:58 GMT
< Content-Length: 0
< 
* Closing connection 0
* Clear auth, redirects to port from 80 to 443
* Issue another request to this URL: 'https://pve-01.mydomain.com/'
*   Trying 111.222.60.148:443...
* Connected to pve-01.mydomain.com (111.222.60.148) port 443 (#1)
* ALPN: offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256
* ALPN: server accepted h2
* Server certificate:
*  subject: CN=pve-01.mydomain.com
*  start date: Jun 28 04:48:08 2025 GMT
*  expire date: Sep 26 04:48:07 2025 GMT
*  issuer: C=US; O=Let's Encrypt; CN=E6
*  SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
* using HTTP/2
* h2h3 [:method: GET]
* h2h3 [:path: /]
* h2h3 [:scheme: https]
* h2h3 [:authority: pve-01.mydomain.com]
* h2h3 [user-agent: curl/7.88.1]
* h2h3 [accept: */*]
* Using Stream ID: 1 (easy handle 0x5b231a0837a0)
> GET / HTTP/2
> Host: pve-01.mydomain.com
> user-agent: curl/7.88.1
> accept: */*
> 
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
< HTTP/2 301 
< alt-svc: h3=":443"; ma=2592000
< cache-control: max-age=0
< date: Sun, 29 Jun 2025 04:04:58 GMT
< expires: Sun, 29 Jun 2025 04:04:58 GMT
< location: https://pve-01.mydomain.com/
< pragma: no-cache
< server: pve-api-daemon/3.0
< via: 1.1 Caddy
< content-type: text/plain; charset=utf-8
< content-length: 17
< 
* Ignoring the response-body
* Connection #1 to host pve-01.mydomain.com left intact
* Issue another request to this URL: 'https://pve-01.mydomain.com/'
* Found bundle for host: 0x5b231a07fee0 [can multiplex]
* Re-using existing connection #1 with host pve-01.mydomain.com
* h2h3 [:method: GET]
* h2h3 [:path: /]
* h2h3 [:scheme: https]
* h2h3 [:authority: pve-01.mydomain.com]
* h2h3 [user-agent: curl/7.88.1]
* h2h3 [accept: */*]
* Using Stream ID: 3 (easy handle 0x5b231a0837a0)
> GET / HTTP/2
> Host: pve-01.mydomain.com
> user-agent: curl/7.88.1
> accept: */*
> 
< HTTP/2 301 
< alt-svc: h3=":443"; ma=2592000
< cache-control: max-age=0
< date: Sun, 29 Jun 2025 04:04:58 GMT
< expires: Sun, 29 Jun 2025 04:04:58 GMT
< location: https://pve-01.mydomain.com/
< pragma: no-cache
< server: pve-api-daemon/3.0
< via: 1.1 Caddy
< content-type: text/plain; charset=utf-8
< content-length: 17
< 
* Ignoring the response-body
* Connection #1 to host pve-01.mydomain.com left intact
* Issue another request to this URL: 'https://pve-01.mydomain.com/'
* Found bundle for host: 0x5b231a07fee0 [can multiplex]
* Re-using existing connection #1 with host pve-01.mydomain.com
* h2h3 [:method: GET]
* h2h3 [:path: /]
* h2h3 [:scheme: https]
* h2h3 [:authority: pve-01.mydomain.com]
* h2h3 [user-agent: curl/7.88.1]
* h2h3 [accept: */*]
* Using Stream ID: 5 (easy handle 0x5b231a0837a0)
> GET / HTTP/2
> Host: pve-01.mydomain.com
> user-agent: curl/7.88.1
> accept: */*
> 
< HTTP/2 301 
< alt-svc: h3=":443"; ma=2592000
< cache-control: max-age=0
< date: Sun, 29 Jun 2025 04:04:58 GMT
< expires: Sun, 29 Jun 2025 04:04:58 GMT
< location: https://pve-01.mydomain.com/
< pragma: no-cache
< server: pve-api-daemon/3.0
< via: 1.1 Caddy
< content-type: text/plain; charset=utf-8
< content-length: 17
< 
* Ignoring the response-body
* Connection #1 to host pve-01.mydomain.com left intact
* Maximum (3) redirects followed
curl: (47) Maximum (3) redirects followed

It looks like Proxmox doesn’t like the way it’s being accessed and keeps redirecting you. The 301 redirect isn’t coming from Caddy - that’s Proxmox. Caddy typically uses 308 redirects.

Try this:

curl -Lvk --max-redirs 3 http://192.168.1.2:8006

I’m assuming port 8006 is using HTTP. If it’s actually HTTPS, use this instead:

curl -Lvk --max-redirs 3 https://192.168.1.2:8006

HTTP command gave me this…

root@caddy:~# curl -Lvk --max-redirs 3 http://192.168.1.2:8006
*   Trying 192.168.1.2:8006...
* Connected to 192.168.1.2 (192.168.1.2) port 8006 (#0)
> GET / HTTP/1.1
> Host: 192.168.1.2:8006
> User-Agent: curl/7.88.1
> Accept: */*
> 
< HTTP/1.1 301 Moved Permanently
< Cache-Control: max-age=0
< Connection: Keep-Alive
< Date: Sun, 29 Jun 2025 05:15:05 GMT
< Pragma: no-cache
< Location: https://192.168.1.2:8006/
< Server: pve-api-daemon/3.0
< Content-Length: 17
< Expires: Sun, 29 Jun 2025 05:15:05 GMT
< 
* Ignoring the response-body
* Connection #0 to host 192.168.1.2 left intact
* Clear auth, redirects scheme from HTTP to https
* Issue another request to this URL: 'https://192.168.1.2:8006/'
* Found bundle for host: 0x5a22ade09440 [serially]
* Can not multiplex, even if we wanted to
* Connection 0 seems to be dead
* Closing connection 0
* Hostname 192.168.1.2 was found in DNS cache
*   Trying 192.168.1.2:8006...
* Connected to 192.168.1.2 (192.168.1.2) port 8006 (#1)
* ALPN: offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN: server did not agree on a protocol. Uses default.
* Server certificate:
*  subject: OU=PVE Cluster Node; O=Proxmox Virtual Environment; CN=pve-01.localdomain
*  start date: Jun 13 10:27:23 2025 GMT
*  expire date: Jun 13 10:27:23 2027 GMT
*  issuer: CN=Proxmox Virtual Environment; OU=b32cacfc-b60a-4ddc-8059-ba40c17a6bd7; O=PVE Cluster Manager CA
*  SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
* using HTTP/1.x
> GET / HTTP/1.1
> Host: 192.168.1.2:8006
> User-Agent: curl/7.88.1
> Accept: */*
> 
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* old SSL session ID is stale, removing
< HTTP/1.1 200 OK
< Cache-Control: max-age=0
< Connection: Keep-Alive
< Date: Sun, 29 Jun 2025 05:15:05 GMT
< Pragma: no-cache
< Server: pve-api-daemon/3.0
< Content-Length: 2507
< Content-Type: text/html; charset=utf-8
< Expires: Sun, 29 Jun 2025 05:15:05 GMT
< 
<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
    <title>pve-01 - Proxmox Virtual Environment</title>
    <link rel="icon" sizes="128x128" href="/pve2/images/logo-128.png" />
    <link rel="apple-touch-icon" sizes="128x128" href="/pve2/images/logo-128.png" />
    <link rel="stylesheet" type="text/css" href="/pve2/ext6/theme-crisp/resources/theme-crisp-all.css?ver=7.0.0" />
    <link rel="stylesheet" type="text/css" href="/pve2/ext6/crisp/resources/charts-all.css?ver=7.0.0" />
    <link rel="stylesheet" type="text/css" href="/pve2/fa/css/font-awesome.css" />
    <link rel="stylesheet" type="text/css" href="/pve2/font-logos/css/font-logos.css" />
    <link rel="stylesheet" type="text/css" href="/pve2/css/ext6-pve.css?ver=8.4.0" />
    <link rel="stylesheet" type="text/css" href="/pwt/css/ext6-pmx.css?ver=v4.3.10-t1744126616" />
    <link rel="stylesheet" type="text/css" media="(prefers-color-scheme: dark)" href="/pwt/themes/theme-proxmox-dark.css?ver=v4.3.10-t1744126616" />
    
    <script type='text/javascript'>function gettext(buf) { return buf; }</script>
    
    <script type="text/javascript" src="/pve2/ext6/ext-all.js?ver=7.0.0"></script>
    <script type="text/javascript" src="/pve2/ext6/charts.js?ver=7.0.0"></script>
    
    <script type="text/javascript" src="/pve2/js/u2f-api.js"></script>
    <script type="text/javascript" src="/qrcode.min.js"></script>
    <script type="text/javascript">
    Proxmox = {
        Setup: { auth_cookie_name: 'PVEAuthCookie' },
        defaultLang: 'en',
        NodeName: 'pve-01',
        UserName: '',
        CSRFPreventionToken: 'null',
        ConsentText: ''
    };
    </script>
    <script type="text/javascript" src="/proxmoxlib.js?ver=v4.3.10-t1744126616"></script>
    <script type="text/javascript" src="/pve2/js/pvemanagerlib.js?ver=8.4.0"></script>
    <script type="text/javascript" src="/pve2/ext6/locale/locale-en.js?ver=7.0.0"></script>

    <script type="text/javascript">
    if (typeof(PVE) === 'undefined') PVE = {};
    Ext.History.fieldid = 'x-history-field';
    Ext.onReady(function() { Ext.create('PVE.StdWorkspace');});
    </script>

  </head>
  <body>
    <!-- Fields required for history management -->
    <form id="history-form" class="x-hidden">
    <input type="hidden" id="x-history-field"/>
    </form>
  </body>
</html>
* Connection #1 to host 192.168.1.2 left intact

HTTPS …

root@caddy:~# curl -Lvk --max-redirs 3 https://192.168.1.2:8006
*   Trying 192.168.1.2:8006...
* Connected to 192.168.1.2 (192.168.1.2) port 8006 (#0)
* ALPN: offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN: server did not agree on a protocol. Uses default.
* Server certificate:
*  subject: OU=PVE Cluster Node; O=Proxmox Virtual Environment; CN=pve-01.localdomain
*  start date: Jun 13 10:27:23 2025 GMT
*  expire date: Jun 13 10:27:23 2027 GMT
*  issuer: CN=Proxmox Virtual Environment; OU=b32cacfc-b60a-4ddc-8059-ba40c17a6bd7; O=PVE Cluster Manager CA
*  SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
* using HTTP/1.x
> GET / HTTP/1.1
> Host: 192.168.1.2:8006
> User-Agent: curl/7.88.1
> Accept: */*
> 
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* old SSL session ID is stale, removing
< HTTP/1.1 200 OK
< Cache-Control: max-age=0
< Connection: Keep-Alive
< Date: Sun, 29 Jun 2025 05:16:40 GMT
< Pragma: no-cache
< Server: pve-api-daemon/3.0
< Content-Length: 2507
< Content-Type: text/html; charset=utf-8
< Expires: Sun, 29 Jun 2025 05:16:40 GMT
< 
<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
    <title>pve-01 - Proxmox Virtual Environment</title>
    <link rel="icon" sizes="128x128" href="/pve2/images/logo-128.png" />
    <link rel="apple-touch-icon" sizes="128x128" href="/pve2/images/logo-128.png" />
    <link rel="stylesheet" type="text/css" href="/pve2/ext6/theme-crisp/resources/theme-crisp-all.css?ver=7.0.0" />
    <link rel="stylesheet" type="text/css" href="/pve2/ext6/crisp/resources/charts-all.css?ver=7.0.0" />
    <link rel="stylesheet" type="text/css" href="/pve2/fa/css/font-awesome.css" />
    <link rel="stylesheet" type="text/css" href="/pve2/font-logos/css/font-logos.css" />
    <link rel="stylesheet" type="text/css" href="/pve2/css/ext6-pve.css?ver=8.4.0" />
    <link rel="stylesheet" type="text/css" href="/pwt/css/ext6-pmx.css?ver=v4.3.10-t1744126616" />
    <link rel="stylesheet" type="text/css" media="(prefers-color-scheme: dark)" href="/pwt/themes/theme-proxmox-dark.css?ver=v4.3.10-t1744126616" />
    
    <script type='text/javascript'>function gettext(buf) { return buf; }</script>
    
    <script type="text/javascript" src="/pve2/ext6/ext-all.js?ver=7.0.0"></script>
    <script type="text/javascript" src="/pve2/ext6/charts.js?ver=7.0.0"></script>
    
    <script type="text/javascript" src="/pve2/js/u2f-api.js"></script>
    <script type="text/javascript" src="/qrcode.min.js"></script>
    <script type="text/javascript">
    Proxmox = {
        Setup: { auth_cookie_name: 'PVEAuthCookie' },
        defaultLang: 'en',
        NodeName: 'pve-01',
        UserName: '',
        CSRFPreventionToken: 'null',
        ConsentText: ''
    };
    </script>
    <script type="text/javascript" src="/proxmoxlib.js?ver=v4.3.10-t1744126616"></script>
    <script type="text/javascript" src="/pve2/js/pvemanagerlib.js?ver=8.4.0"></script>
    <script type="text/javascript" src="/pve2/ext6/locale/locale-en.js?ver=7.0.0"></script>

    <script type="text/javascript">
    if (typeof(PVE) === 'undefined') PVE = {};
    Ext.History.fieldid = 'x-history-field';
    Ext.onReady(function() { Ext.create('PVE.StdWorkspace');});
    </script>

  </head>
  <body>
    <!-- Fields required for history management -->
    <form id="history-form" class="x-hidden">
    <input type="hidden" id="x-history-field"/>
    </form>
  </body>
</html>
* Connection #0 to host 192.168.1.2 left intact

Is there a certain Caddyfile syntax that will help me overcome Proxmox’s 301 redirect?

That’s why we’re troubleshooting it first to find out what’s going on. You didn’t provide much information in your original post :slight_smile:

Change this:

pve-01.mydomain.com {
        reverse_proxy 192.168.1.2:8006
}

to this:

pve-01.mydomain.com {
        reverse_proxy https://192.168.1.2:8006 {
                transport http {
                        tls_insecure_skip_verify
                }
        }
}
2 Likes

Didn’t I? So sorry!

You sir - ROCK!!! I’m in business now.

Thank you very much!

Very grateful indeed. Appreciate your time for me. !!!

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.