Bug: Absolute redirect URL with internal port generated on session reconnect when behind reverse proxy (4.20.1)

Hi ThinLinc team,

I would like to report a bug in ThinLinc 4.20.1 affecting the reverse proxy support introduced in 4.20.

Summary

When a user reconnects to an existing session through a reverse proxy, tlwebaccess generates an absolute redirect URL containing the internal listen_port (e.g. https://hostname:300/agent), bypassing the reverse proxy entirely. This does not occur when starting a new session.

Environment

  • ThinLinc version: 4.20.1 (build 4529)
  • Reverse proxy: Caddy
  • OS: Debian/Ubuntu
  • Single-server setup

Steps to Reproduce

  1. Configure tlwebaccess behind a reverse proxy on port 443
  2. Set trusted_proxies=127.0.0.1 in webaccess.hconf
  3. Log in via the proxy — a new session is created and the redirect works correctly (relative URL)
  4. Log out and log in again — an existing session is found and the browser is redirected to https://hostname:300/agent (absolute URL with internal port), bypassing the proxy

Root Cause

In modules/thinlinc/tlwebaccess/main.py there are two code paths for building redirect_target:

New session (correct — relative URL):

i1111IIi = "connect/%s/agent" % ii1ii

Reconnect to existing session (broken — absolute URL with internal port):

I1i = hive.get_integer("/webaccess/listen_port", 300)
i1111IIi = "https://%s:%s/agent" % (OO0000, I1i)

The reconnect path was not updated when reverse proxy support was introduced. It still builds an absolute URL using listen_port instead of a relative URL like the new-session path does.

Workaround

Setting login_page=https://hostname:443/ in webaccess.hconf causes ThinLinc to use the correct public URL. However, this parameter should not be required for basic reverse proxy operation, and the inconsistency between the two code paths is the underlying issue.

Expected Behavior

Both code paths should generate a relative redirect URL, e.g.:

i1111IIi = "connect/%s/agent" % OO0000

This would make reconnect behavior consistent with new session behavior and fully transparent to any reverse proxy configuration.

Thanks for the great work on 4.20 reverse proxy support — this is the last remaining piece that needs fixing.

Best regards,
Ginsterkatze

Additional finding: The bug cannot be reproduced when Caddy explicitly handles /connect/<hostname>/* as a separate handle_path block.

Evidence from logs:

The Caddy access log shows the last proxied request from the client is POST /connect/<hostname>/agent (status 200). No further requests from the client appear in the Caddy log — the browser connects directly to port 300 after receiving the response.

The tlwebaccess log confirms the reconnect code path is triggered: User '<user>' reconnecting to existing session. Source IP [::1] confirms Caddy is proxying correctly, but the generated HTML contains an absolute URL with the internal port, which the browser then accesses directly.

Hello Ginsterkatze!

Thanks for the report. I attempted to reproduce the issue you described using Ubuntu 24.04, ThinLinc 4.20.1 and Caddy, but I was unable to replicate the behaviour. When setting up ThinLinc Web Access behind a reverse proxy, certain configuration parameters are mandatory to ensure consistent behaviour. I have a few points to clarify the situation:

  1. When you mention “log out and log in again,” are you referring to the behaviour after clicking the Return button following a session disconnect? The destination of that button is explicitly controlled by the /webaccess/login_page parameter. According to the ThinLinc Reverse Proxy documentation, it is a requirement to update /webaccess/login_page to match your public URL.

  2. The documentation also states that the proxy must have an explicit path handler for every agent in the cluster (e.g., /connect//). Even in a single-server setup, the master acts as an agent and requires this handler to correctly route session traffic. Make sure that vsmagent/agent_hostname is explicitly set to a resolvable hostname.

Here is a example of the Caddyfile used during my testing:

thinlinc.example.com {
    tls /etc/caddy/certs/server.crt /etc/caddy/certs/server.key

    reverse_proxy https://internal-server.local:300 {
        transport http {
            # Required if ThinLinc uses a self-signed certificate internally
            tls_insecure_skip_verify
        }
    }

    handle_path /connect/agent1.internal.example.com/* {
        reverse_proxy https://internal-server.local:300 {
            transport http {
                tls_insecure_skip_verify
            }
        }
    }
}

Please let me know if the issue persists,

Madeleine

Thank you Madeleine,

especially for pointing to the handle_path neccessity in reverse proxy settings!

I’m now not able to reproduce the oberserved behaviour either. My fault obviously.

For me the most crucial part was the correct setup for cadddy as the doumention mentions Nginx:

handle_path /connect//\* { reverse_proxy https://localhost:300 { header_up Host {host} header_up X-Real-IP {remote_host} transport http { tls_insecure_skip_verify } } } reverse_proxy https://localhost:300 { header_up Host {host} header_up X-Real-IP {remote_host} transport http { tls_insecure_skip_verify } }

In my case the /webaccess/login_page setting does not matter.
Both the default entry “/” and ":443/ work.

Kind Regards

Matthias