There doesn't seem to be an easy way to get Pangolin to run properly on alternate ports. Simple changing the mapping does not work.
For example, you could have in docker-compose.yml
ports:
- 8443:443 # Port for traefik because of the network_mode
- 8080:80 # Port for traefik because of the network_mode
OR, in docker-compose.yml
ports:
- 8443:8443 # Port for traefik because of the network_mode
- 8080:8080 # Port for traefik because of the network_mode
This allows access to the admin interface on the alternate HTTPS port. However, once you create a resource, it will never match up and results in an error such as "Unauthorized" (and "access denied" in the logs). This is because the check will also compare the alternate port, while the resource entry apparently expects the default HTTPS port (443). You also connect specify a port in the WebUI for the resource (colon isn't an allow character).
Strip the port when matching against a resource
or
Allow users to enter a port with the resource
(there are many reasons to run on ports other than 80+443, including firewall issues/configs or if an existing webserver etc is also binding that port)
Originally created by @Error-Gap on GitHub (Jul 26, 2025).
Original GitHub issue: https://github.com/fosrl/pangolin/issues/1133
There doesn't seem to be an easy way to get Pangolin to run properly on alternate ports. Simple changing the mapping does not work.
For example, you could have in docker-compose.yml
```
ports:
- 8443:443 # Port for traefik because of the network_mode
- 8080:80 # Port for traefik because of the network_mode
```
OR, in docker-compose.yml
```
ports:
- 8443:8443 # Port for traefik because of the network_mode
- 8080:8080 # Port for traefik because of the network_mode
```
and traefik_config.yml
```
entryPoints:
web:
address: "8080"
websecure:
address: ":8443"
```
This allows access to the admin interface on the alternate HTTPS port. However, once you create a resource, it will never match up and results in an error such as "Unauthorized" (and "access denied" in the logs). This is because the check will also compare the alternate port, while the resource entry apparently expects the default HTTPS port (443). You also connect specify a port in the WebUI for the resource (colon isn't an allow character).
Error Snippet (IP's and domain masked)
```
pangolin | 2025-07-26T03:38:51.241Z [debug]: Verify session: Badger sent {"sessions":{},"originalRequestURL":"https://alt.mysite.com:8443/","scheme":"","host":"alt.mysite.com:8443","path":"/","method":"GET","tls":true,"requestIp":"1.2.3.4:41409","headers":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8","Accept-Encoding":"gzip, deflate, br, zstd","Accept-Language":"en-CA,en-US;q=0.7,en;q=0.3","Priority":"u=0, i","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"none","Sec-Fetch-User":"?1","Sec-Gpc":"1","Te":"trailers","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (X11; Linux x86_64; rv:141.0) Gecko/20100101 Firefox/141.0","X-Forwarded-Host":"alt.mysite.com:8443","X-Forwarded-Port":"8443","X-Forwarded-Proto":"https","X-Forwarded-Server":"7d0cda26f0c","X-Real-Ip":"1.2.3.4"}}
pangolin | 2025-07-26T03:38:51.244Z [debug]: Request IP: {"requestIp":"1.2.3.4:41409"}
pangolin | 2025-07-26T03:38:51.246Z [debug]: Client IP: {"clientIp":"6.7.8.9"}
pangolin | 2025-07-26T03:38:51.249Z [debug]: Resource not found {"0":"a","1":"l","2":"t","3":".","4":"m","5":"y","6":"s","7":"i","8":"t","9":"e","10":".","11":"c","12":"o","13":"m","14":"8","15":"4","16":"4","17":"3"}
pangolin | 2025-07-26T03:38:51.250Z [debug]: {"data":{"valid":false},"success":true,"error":false,"message":"Access denied","status":200}
```
Suggestion:
- Strip the port when matching against a resource
or
- Allow users to enter a port with the resource
(there are many reasons to run on ports other than 80+443, including firewall issues/configs or if an existing webserver etc is also binding that port)
logger.debug("Client IP:", { clientIp });
let cleanHost = host;
// if the host ends with :443 or :80 remove it
if (cleanHost.endsWith(":443")) {
cleanHost = cleanHost.slice(0, -4);
} else if (cleanHost.endsWith(":80")) {
cleanHost = cleanHost.slice(0, -3);
The above appears to slice off the port from the end of the FQDN, but only if the port is 443 or 80, which means it is going to not respect the entryPoint ports as defined in the traefik config, port bindings defined in the docker-compose file, traffic NAT'ing, etc
Rather than hardcoding 443 or 80, it might make more sense to slice off any numeric values following after the first colon
<!-- gh-comment-id:3121340386 -->
@Error-Gap commented on GitHub (Jul 26, 2025):
Traced this down to the following in Badger
```
logger.debug("Client IP:", { clientIp });
let cleanHost = host;
// if the host ends with :443 or :80 remove it
if (cleanHost.endsWith(":443")) {
cleanHost = cleanHost.slice(0, -4);
} else if (cleanHost.endsWith(":80")) {
cleanHost = cleanHost.slice(0, -3);
```
The above appears to slice off the port from the end of the FQDN, but _only_ if the port is 443 or 80, which means it is going to not respect the entryPoint ports as defined in the traefik config, port bindings defined in the docker-compose file, traffic NAT'ing, etc
Rather than hardcoding 443 or 80, it might make more sense to slice off any numeric values following after the first colon
Have forked out for some experimentation over the weekend. Altering the updated section to slice off any "port" section of the host does work in terms of having the appropriate resource matched and auth page show up, but will still run into errors elsewhere in the code where the current port being used does not match the resource entry (in particular, the post successful-auth redirect will send the user back to the HTTPS port).
Still running some trials on this but if I can get it working in the fork I will hopefully be able to provide the appropriate updates (so no need for the project devs to spend time looking into this as of current).
<!-- gh-comment-id:3125800483 -->
@Error-Gap commented on GitHub (Jul 28, 2025):
Have forked out for some experimentation over the weekend. Altering the updated section to slice off any "port" section of the host _does_ work in terms of having the appropriate resource matched and auth page show up, but will still run into errors elsewhere in the code where the current port being used does not match the resource entry (in particular, the post successful-auth redirect will send the user back to the HTTPS port).
Still running some trials on this but if I can get it working in the fork I will hopefully be able to provide the appropriate updates (so no need for the project devs to spend time looking into this as of current).
@oschwartz10612 commented on GitHub (Jul 28, 2025):
Ahh yep that makes sense I would 100 agree thats the issue in Badger. Also take a look at verifySession.ts but I think that is probably good.
Thanks for finding this. If you want to open a PR that would be incredible!
<!-- gh-comment-id:3128358251 -->
@oschwartz10612 commented on GitHub (Jul 28, 2025):
Ahh yep that makes sense I would 100 agree thats the issue in Badger. Also take a look at `verifySession.ts` but I think that is probably good.
Thanks for finding this. If you want to open a PR that would be incredible!
Will do. I've resolved the issue in
server/routers/badger/verifySession.ts
There's another in
pangolin/src/app/auth/resource/[resourceId]/page.tsx
Just sorting that out then I'll request a pull for the forked updates, assuming nothing else comes up.
<!-- gh-comment-id:3130855040 -->
@Error-Gap commented on GitHub (Jul 29, 2025):
Will do. I've resolved the issue in
server/routers/badger/verifySession.ts
There's another in
pangolin/src/app/auth/resource/[resourceId]/page.tsx
Just sorting that out then I'll request a pull for the forked updates, assuming nothing else comes up.
Wanted to say thanks for taking a swing at supporting alternative ports. I just tried to setup Pangolin on my server that's already running web services on 80 and 443, and hit this issue. Looking forward to your fixes.
<!-- gh-comment-id:3134235053 -->
@Linuturk commented on GitHub (Jul 29, 2025):
Wanted to say thanks for taking a swing at supporting alternative ports. I just tried to setup Pangolin on my server that's already running web services on 80 and 443, and hit this issue. Looking forward to your fixes.
Finally looking like it works on the alt-port, but had to make changes in:
server/routers/badger/verifySession.ts : verifyResourceSession() allowed stripping port from the cleanHost variable (when it's something other than just 80 or 443). This allows the resource page to show up a login, but the page will still throw you back to port 443 https
src/app/auth/resource/[resourceId]/page.tsx : Had to account for the current port when doing the comparison between serverResourceHost and redirectHost so that redirectURL would be read in with the alternate port. No more being thrown back to 443, but successful auth was resulting in infinite redirects
exchangeSession.ts : Updated exchangeSession() to do the same port stripping as in verifyResourceSession() so that the session cookie matches up when read later by extractResourceSessionToken()
This seems to be successful thus far in that accessing a resource URL properly forwards to the login page, which then redirects back to the resource URL and allows access to the remote resource.
I will need to do a bit more testing to ensure that this doesn't visible break something elsewhere, as well as revert the relevant files back to versions without the crap-ton of debugging outputs I added to trace flow. :-)
Once I've got a clean+working version and done a few more tests I'll commit it back to my fork and then submit a PR to have the changes pulled back into the proper project (this one)
<!-- gh-comment-id:3135127238 -->
@Error-Gap commented on GitHub (Jul 30, 2025):
Finally looking like it works on the alt-port, but had to make changes in:
- server/routers/badger/verifySession.ts : verifyResourceSession() allowed stripping port from the cleanHost variable (when it's something other than just 80 or 443). This allows the resource page to show up a login, but the page will still throw you back to port 443 https
- src/app/auth/resource/[resourceId]/page.tsx : Had to account for the current port when doing the comparison between serverResourceHost and redirectHost so that redirectURL would be read in with the alternate port. No more being thrown back to 443, but successful auth was resulting in infinite redirects
- exchangeSession.ts : Updated exchangeSession() to do the same port stripping as in verifyResourceSession() so that the session cookie matches up when read later by extractResourceSessionToken()
This seems to be successful thus far in that accessing a resource URL properly forwards to the login page, which then redirects back to the resource URL and allows access to the remote resource.
I will need to do a bit more testing to ensure that this doesn't visible break something elsewhere, as well as revert the relevant files back to versions without the crap-ton of debugging outputs I added to trace flow. :-)
Once I've got a clean+working version and done a few more tests I'll commit it back to my fork and then submit a PR to have the changes pulled back into the proper project (this one)
While running on an alternate bound port, I was able to:
Login to admin control-panel and create web resources
Then, in a separate browser/user:
Accessed the associated subdomain on the alternate port
Presented with login page, and logged in successfully
Was able to access the page from the remote resource(s) the user was granted access to
Got the expected "permission denied" error from resource pages the user was not granted access to
Pretty quick smoke-test but it seems good so I've submitted the the PR
<!-- gh-comment-id:3138542249 -->
@Error-Gap commented on GitHub (Jul 31, 2025):
Hi @oschwartz10612
Testing seems OK:
While running on an alternate bound port, I was able to:
- Login to admin control-panel and create web resources
Then, in a separate browser/user:
- Accessed the associated subdomain on the alternate port
- Presented with login page, and logged in successfully
- Was able to access the page from the remote resource(s) the user was granted access to
- Got the expected "permission denied" error from resource pages the user was not granted access to
Pretty quick smoke-test but it seems good so I've submitted the the PR
With OpenZITI things like this (the ports various services listen on), can be set via ENV vars for specific ports set in the .env file or the docker compose file.
<!-- gh-comment-id:3138944681 -->
@bodleytunes commented on GitHub (Jul 31, 2025):
With OpenZITI things like this (the ports various services listen on), can be set via ENV vars for specific ports set in the .env file or the docker compose file.
I'm successfully running Pangolin on a different port, by modifying a couple of files. For future reference, these steps worked for me:
First, I installed Pangolin normally with the installer. Note that during the installation, I didn't specify the custom port anywhere yet, so if ports 80 and 443 are unavailable for you, your mileage may vary here.
Then, I had to ofcourse modify the open port. I'll be running it on port 9443. Since a custom port is used, the insecure port serves no purpose anymore (also not for ACME, see below).
# file: docker-compose.ymlservices:gerbil:ports:- 51820:51820/udp- 21820:21820/udp- 9443:443# note the change here#- 80:80 # this one is commented out entirely
After that, I had to modify the Pangolin config, as it seems that absolute URI references are used in the UI code instead of relative references. The required modifications were:
Lastly, if the server isn't running on ports 80/443, LetsEncrypt is (currently) only able to issue HTTPS certificates with the DNS-01 ACME challenge, instead of the default HTTP-01 challenge, so the config needs to be updated accordingly. Pangolin uses Traefik, which uses the lego library for ACME challenges. Since configuration depends on your DNS provider, see the lego documentation for supported providers and the configuration. To change the challenge type, I modified two files:
# file: config/traefik/traefik_config.ymlcertificatesResolvers:letsencrypt:acme:# remove the httpChallenge key entirely herednsChallenge:provider:[provider]# the code/cli name of the provider as specified in the lego documentation
# file: docker-compose.ymlservices:traefik:environment:# if the lego documentation specifies environment variables, put them here# example:DNS_PROVIDER_API_KEY:api_key_value
And of course, don't forget to restart the server with docker compose down and then docker compose up -d
These steps resulted in a successful installation for me.
<!-- gh-comment-id:3707091009 -->
@jasperweyne commented on GitHub (Jan 3, 2026):
I'm successfully running Pangolin on a different port, by modifying a couple of files. For future reference, these steps worked for me:
- First, I installed Pangolin normally with the installer. Note that during the installation, I didn't specify the custom port anywhere yet, so if ports 80 and 443 are unavailable for you, your mileage may vary here.
- Then, I had to ofcourse modify the open port. I'll be running it on port 9443. Since a custom port is used, the insecure port serves no purpose anymore (also not for ACME, see below).
```yml
# file: docker-compose.yml
services:
gerbil:
ports:
- 51820:51820/udp
- 21820:21820/udp
- 9443:443 # note the change here
#- 80:80 # this one is commented out entirely
```
- After that, I had to modify the Pangolin config, as it seems that absolute URI references are used in the UI code instead of relative references. The required modifications were:
```yml
# file: config/config.yml
app:
dashboard_url: "https://[pangolin domain]:9443"
server:
cors:
origins: ["https://[pangolin domain]:9443"]
```
- Lastly, if the server isn't running on ports 80/443, LetsEncrypt is (currently) only able to issue HTTPS certificates with the DNS-01 ACME challenge, instead of the default HTTP-01 challenge, so the config needs to be updated accordingly. Pangolin uses Traefik, which uses the lego library for ACME challenges. Since configuration depends on your DNS provider, see the [lego documentation](https://go-acme.github.io/lego/dns/index.html) for supported providers and the configuration. To change the challenge type, I modified two files:
```yml
# file: config/traefik/traefik_config.yml
certificatesResolvers:
letsencrypt:
acme:
# remove the httpChallenge key entirely here
dnsChallenge:
provider: [provider] # the code/cli name of the provider as specified in the lego documentation
```
```yml
# file: docker-compose.yml
services:
traefik:
environment:
# if the lego documentation specifies environment variables, put them here
# example:
DNS_PROVIDER_API_KEY: api_key_value
```
- And of course, don't forget to restart the server with `docker compose down` and then `docker compose up -d`
These steps resulted in a successful installation for me.
The above reply was very helpful to me. I had to move the pangolin dashboard so I could forward port 443 as a raw TCP resource.
<!-- gh-comment-id:3940432687 -->
@coavins commented on GitHub (Feb 22, 2026):
The above reply was very helpful to me. I had to move the pangolin dashboard so I could forward port 443 as a raw TCP resource.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Originally created by @Error-Gap on GitHub (Jul 26, 2025).
Original GitHub issue: https://github.com/fosrl/pangolin/issues/1133
There doesn't seem to be an easy way to get Pangolin to run properly on alternate ports. Simple changing the mapping does not work.
For example, you could have in docker-compose.yml
OR, in docker-compose.yml
and traefik_config.yml
This allows access to the admin interface on the alternate HTTPS port. However, once you create a resource, it will never match up and results in an error such as "Unauthorized" (and "access denied" in the logs). This is because the check will also compare the alternate port, while the resource entry apparently expects the default HTTPS port (443). You also connect specify a port in the WebUI for the resource (colon isn't an allow character).
Error Snippet (IP's and domain masked)
Suggestion:
or
(there are many reasons to run on ports other than 80+443, including firewall issues/configs or if an existing webserver etc is also binding that port)
@Error-Gap commented on GitHub (Jul 26, 2025):
Traced this down to the following in Badger
The above appears to slice off the port from the end of the FQDN, but only if the port is 443 or 80, which means it is going to not respect the entryPoint ports as defined in the traefik config, port bindings defined in the docker-compose file, traffic NAT'ing, etc
Rather than hardcoding 443 or 80, it might make more sense to slice off any numeric values following after the first colon
@Error-Gap commented on GitHub (Jul 28, 2025):
Have forked out for some experimentation over the weekend. Altering the updated section to slice off any "port" section of the host does work in terms of having the appropriate resource matched and auth page show up, but will still run into errors elsewhere in the code where the current port being used does not match the resource entry (in particular, the post successful-auth redirect will send the user back to the HTTPS port).
Still running some trials on this but if I can get it working in the fork I will hopefully be able to provide the appropriate updates (so no need for the project devs to spend time looking into this as of current).
@oschwartz10612 commented on GitHub (Jul 28, 2025):
Ahh yep that makes sense I would 100 agree thats the issue in Badger. Also take a look at
verifySession.tsbut I think that is probably good.Thanks for finding this. If you want to open a PR that would be incredible!
@Error-Gap commented on GitHub (Jul 29, 2025):
Will do. I've resolved the issue in
server/routers/badger/verifySession.ts
There's another in
pangolin/src/app/auth/resource/[resourceId]/page.tsx
Just sorting that out then I'll request a pull for the forked updates, assuming nothing else comes up.
@Linuturk commented on GitHub (Jul 29, 2025):
Wanted to say thanks for taking a swing at supporting alternative ports. I just tried to setup Pangolin on my server that's already running web services on 80 and 443, and hit this issue. Looking forward to your fixes.
@Error-Gap commented on GitHub (Jul 30, 2025):
Finally looking like it works on the alt-port, but had to make changes in:
This seems to be successful thus far in that accessing a resource URL properly forwards to the login page, which then redirects back to the resource URL and allows access to the remote resource.
I will need to do a bit more testing to ensure that this doesn't visible break something elsewhere, as well as revert the relevant files back to versions without the crap-ton of debugging outputs I added to trace flow. :-)
Once I've got a clean+working version and done a few more tests I'll commit it back to my fork and then submit a PR to have the changes pulled back into the proper project (this one)
@Error-Gap commented on GitHub (Jul 31, 2025):
Hi @oschwartz10612
Testing seems OK:
While running on an alternate bound port, I was able to:
Then, in a separate browser/user:
Pretty quick smoke-test but it seems good so I've submitted the the PR
@bodleytunes commented on GitHub (Jul 31, 2025):
With OpenZITI things like this (the ports various services listen on), can be set via ENV vars for specific ports set in the .env file or the docker compose file.
@jasperweyne commented on GitHub (Jan 3, 2026):
I'm successfully running Pangolin on a different port, by modifying a couple of files. For future reference, these steps worked for me:
docker compose downand thendocker compose up -dThese steps resulted in a successful installation for me.
@coavins commented on GitHub (Feb 22, 2026):
The above reply was very helpful to me. I had to move the pangolin dashboard so I could forward port 443 as a raw TCP resource.