Keycloak for Linux Remote Desktop: ThinLinc OIDC Setup

ThinLinc 4.21.0 introduced native OpenID Connect (OIDC) authentication for ThinLinc Web Access. This allows you to use an external identity provider such as Keycloak to authenticate users before starting their Linux remote desktop sessions.

With this configuration, users open ThinLinc Web Access in their browser and click Sign in with Keycloak. ThinLinc redirects the browser to Keycloak, Keycloak authenticates the user, and ThinLinc maps the returned OIDC username claim to an existing Linux account.

Prerequisites

Before you begin, you need:

  • Ubuntu Server 24.04 LTS.
  • Root access or a sudo-enabled user.
  • A public IPv4 address.
  • DNS records for ThinLinc.
  • TCP ports 22, 80, and 443 open.
  • ThinLinc 4.21.0 or later.

Part I - Install ThinLinc Server

Step 1 - Update Ubuntu 24.04

Update the package index:

sudo apt update

Upgrade installed packages:

sudo apt upgrade -y

Install the required packages:

sudo apt install -y wget unzip curl openssh-server dnsutils nginx certbot python3-certbot-nginx

Step 2 - Configure the Server Hostname

Set the fully qualified hostname:

sudo hostnamectl set-hostname thinlinc.linuxbuz.com

Verify it:

hostnamectl
hostname -f

Expected output:

thinlinc.linuxbuz.com

Step 3 - Create the ThinLinc Administrator User

Create a Linux account:

sudo adduser thinadm

Add it to the sudo group:

sudo usermod -aG sudo thinadm

Verify the account:

getent passwd thinadm

Example output:

thinadm:x:1000:1000:,,,:/home/thinadm:/bin/bash

Switch to the new user:

su - thinadm

Verify sudo access:

sudo -v

Step 4 - Add ThinLinc Commands to sudo secure_path

Open the sudoers file safely:

sudo visudo

Find the Defaults secure_path line and add the ThinLinc paths:

Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin:/opt/thinlinc/bin:/opt/thinlinc/sbin"

Save and exit.


Step 5 - Download ThinLinc Server 4.21.0

Download ThinLinc:

wget https://www.cendio.com/downloads/server/tl-4.21.0-server.zip

Extract it:

unzip tl-4.21.0-server.zip

Enter the extracted directory:

cd tl-4.21.0-server

Step 6 - Install ThinLinc Server

Run the installer:

sudo ./install-server

If GTK+ is unavailable, ThinLinc automatically falls back to text mode:

Could not start GTK+. Using text mode fallback...

Press Enter when prompted.

The installer detects the ThinLinc package:

thinlinc-server_4.21.0-4708_amd64.deb

Press Enter again to install it.

After installation, you should see:

Installation of ThinLinc completed!

When prompted:

Run ThinLinc setup now [Yes/no]?

Enter:

Yes

Step 7 - Complete ThinLinc Setup

Accept the license agreement:

Do you accept the terms of the license agreement [yes/No]? yes

Select the first server as the master:

Server type [Master/agent]? Master

Allow ThinLinc to analyze the system.

When prompted for NFS support, select:

Automatically install the necessary packages [Yes/no]? Yes

When prompted for Python LDAP support, select:

Automatically install the necessary packages [Yes/no]? Yes

For the externally reachable address, select:

hostname

ThinLinc should detect:

thinlinc.linuxbuz.com

Enter an administrator email address when requested.

If you do not need printer redirection:

Configure location-based printer queue (nearest) [Yes/no]? no
Configure client-side printer queue (thinlocal) [Yes/no]? no

Install the ThinLinc AppArmor configuration:

Install AppArmor configuration [Yes/no]? Yes

After setup finishes, reboot:

sudo reboot

Step 8 - Verify ThinLinc Services

After reboot, check:

sudo systemctl status vsmserver
sudo systemctl status vsmagent
sudo systemctl status tlwebaccess

Each enabled service should show:

Active: active (running)

Verify the agent hostname:

/opt/thinlinc/bin/tl-config /vsmagent/agent_hostname

Expected:

thinlinc.linuxbuz.com

If needed, set it explicitly:

sudo /opt/thinlinc/bin/tl-config /vsmagent/agent_hostname=thinlinc.linuxbuz.com

Restart the agent:

sudo systemctl restart vsmagent

Part II - Publish ThinLinc Through Nginx

Step 9 - Verify ThinLinc Web Access

ThinLinc Web Access normally listens on HTTPS port 300.

Check it:

sudo ss -lntp | grep :300

Step 10 - Configure Nginx for ThinLinc

Create the Nginx site:

sudo nano /etc/nginx/sites-available/thinlinc

Add:

server {
    listen 80;
    listen [::]:80;

    server_name thinlinc.linuxbuz.com;

    proxy_read_timeout 999h;

    location / {
        proxy_pass https://127.0.0.1:300/;
        proxy_http_version 1.1;

        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $http_connection;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location /connect/thinlinc.linuxbuz.com/ {
        proxy_pass https://127.0.0.1:300/;
        proxy_http_version 1.1;

        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $http_connection;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Enable the site:

sudo ln -s /etc/nginx/sites-available/thinlinc /etc/nginx/sites-enabled/thinlinc

Remove the default site:

sudo rm -f /etc/nginx/sites-enabled/default

Test Nginx:

sudo nginx -t

Reload Nginx:

sudo systemctl reload nginx

Step 11 - Configure ThinLinc for the Reverse Proxy

Set the public login page:

sudo /opt/thinlinc/bin/tl-config /webaccess/login_page=https://thinlinc.linuxbuz.com/

Trust the local Nginx proxy:

sudo /opt/thinlinc/bin/tl-config /webaccess/trusted_proxies=127.0.0.1

Restart Web Access:

sudo systemctl restart tlwebaccess

Step 12 - Configure the Firewall

Allow SSH:

sudo ufw allow OpenSSH

Allow HTTP:

sudo ufw allow 80/tcp

Allow HTTPS:

sudo ufw allow 443/tcp

Enable UFW:

sudo ufw enable

Check the rules:

sudo ufw status numbered

When Nginx handles browser access, port 300 does not need to be exposed publicly.


Step 13 - Install a TLS Certificate for ThinLinc

Request a Let’s Encrypt certificate:

sudo certbot --nginx -d thinlinc.linuxbuz.com

Enable HTTPS redirection when prompted.

Test renewal:

sudo certbot renew --dry-run

Open your web browser and verify the ThinLinc web access using the URL:

https://thinlinc.linuxbuz.com

The ThinLinc OIDC redirect URI will be:

https://thinlinc.linuxbuz.com/


Part III - Configure Keycloak for ThinLinc

Step 14 - Open the Keycloak Admin Console

Open the web browser and access your Keycloak Admin Console.

Log in with your admin username and password.


Step 15 - Create the Keycloak Realm

Click the realm selector and click Manage realms.

Click on Create realm.

Enter Realm name:

Realm name: thinlinc

Leave it enabled and click Create.

Step 16 - Create the ThinLinc User

Open Users from the Keycloak console.

Click Create new user.

Set:

Username: thinadm

Optionally enter:

Email
First name
Last name

For Required user actions, leave the field empty for the initial setup.

Set:

Email verified: Off

unless you are actually using email verification.

Click Create.

Set the User Password

Go to Users > thinadm > Credentials tab.

Click Set password.

Enter a strong password, set: “Temporary: Off” and click Save to save the password.

For this tutorial:

Keycloak username = thinadm
Linux username    = thinadm


Step 17 - Create the ThinLinc OIDC Client

On the Keycloak console, click Client > Create client.

Configure the following values:

Client type: OpenID Connect
Client ID: thinlinc-webaccess
Name: ThinLinc Web Access

Click Next.


Step 18 - Configure the Authentication Flow

Click Capability config and set the following settings:

Client authentication: On
Standard flow: On

Disable options that are not required:

Direct access grants: Off
Implicit flow: Off
Service accounts roles: Off
Authorization: Off

Click Next.


Step 19 - Configure the Redirect URI

Provide your ThinLinc URLs:

Root URL:https://thinlinc.linuxbuz.com/
Home URL:https://thinlinc.linuxbuz.com/
Valid redirect URIs: https://thinlinc.linuxbuz.com/

For this server-side OIDC flow, Web origins can normally remain empty.

Click Save to the client.


Step 20 - Copy the Keycloak Client Secret

Open Clients > thinlinc-webaccess > Credentials

Copy the generated Client secret and save it for later use.

You now have:

Client ID:thinlinc-webaccess
Client Secret:<GENERATED_SECRET>

Do not publish or paste the client secret into documentation.


Step 21 - Verify the Keycloak Realm Endpoint

Open the URL below in your web browser to verify the Keycloak realm endpoint.

https://your-keycloak-domain/realms/thinlinc/

A response similar to the following is normal:

realm = "thinlinc"
token-service = "https://your-keycloak-domain/realms/thinlinc/protocol/openid-connect"
account-service = "https://your-keycloak-domain/realms/thinlinc/account"

This confirms that the realm exists.

However, this is not the OIDC discovery endpoint.


Step 22 - Verify the OIDC Discovery URL

The correct discovery URL is:

https://your-keycloak-domain/realms/thinlinc/.well-known/openid-configuration

Test it:

curl -fsSL 'https://your-keycloak-domain/realms/thinlinc/.well-known/openid-configuration' | python3 -m json.tool | head -40

You should see values including:

issuer
authorization_endpoint
token_endpoint
userinfo_endpoint
jwks_uri

The issuer should be:

https://your-keycloak-domain/realms/thinlinc

The JWKS endpoint should be similar to:

https://your-keycloak-domain/realms/thinlinc/protocol/openid-connect/certs

Test it:

curl -fsSL 'https://your-keycloak-domain/realms/thinlinc/protocol/openid-connect/certs' | python3 -m json.tool | head -30

Part IV - Configure ThinLinc OIDC from the Command Line

Step 23 - Store the Keycloak Client Secret

Follow the steps below on the ThinLinc server.

Create a protected directory:

sudo install -d -m 700 -o root -g root /opt/thinlinc/etc/tlwebaccess

Read the client secret into a temporary shell variable:

read -rsp 'Paste Keycloak client secret: ' SECRET
echo

Paste the Keycloak client secret and press Enter.

Write it to the file:

printf '%s' "$SECRET" | sudo tee /opt/thinlinc/etc/tlwebaccess/keycloak_secret.txt >/dev/null

Clear the variable:

unset SECRET

Set ownership:

sudo chown root:root /opt/thinlinc/etc/tlwebaccess/keycloak_secret.txt

Set secure permissions:

sudo chmod 600 /opt/thinlinc/etc/tlwebaccess/keycloak_secret.txt

Verify:

sudo ls -l /opt/thinlinc/etc/tlwebaccess/keycloak_secret.txt

Expected permissions:

-rw------- 1 root root ... keycloak_secret.txt

Important: Do not leave this file at mode 644. The client secret must not be readable by other users.


Step 24 - Check the Secret File Safely

You can verify that the file does not contain unwanted whitespace without printing the secret:

sudo python3 - <<'PY'
p = "/opt/thinlinc/etc/tlwebaccess/keycloak_secret.txt"
data = open(p, "rb").read()

print("Length:", len(data))
print("Ends with newline:", data.endswith(b"\n"))
print("Ends with CRLF:", data.endswith(b"\r\n"))
print("Leading whitespace:", len(data) != len(data.lstrip()))
print("Trailing whitespace:", len(data) != len(data.rstrip()))
PY

Ideally:

Ends with newline: False
Ends with CRLF: False
Leading whitespace: False
Trailing whitespace: False

Step 25 - Configure the Keycloak OIDC Provider in ThinLinc

Set the username claim:

sudo /opt/thinlinc/bin/tl-config /webaccess/oidc/keycloak/username_claim=preferred_username

Set the Keycloak client ID:

sudo /opt/thinlinc/bin/tl-config /webaccess/oidc/keycloak/client_id=thinlinc-webaccess

Set the client-secret file:

sudo /opt/thinlinc/bin/tl-config /webaccess/oidc/keycloak/client_secret_path=/opt/thinlinc/etc/tlwebaccess/keycloak_secret.txt

Set the Keycloak discovery URL:

sudo /opt/thinlinc/bin/tl-config '/webaccess/oidc/keycloak/discovery_url=https://your-keycloak-domain/realms/thinlinc/.well-known/openid-configuration'

Add the profile scope:

sudo /opt/thinlinc/bin/tl-config /webaccess/oidc/keycloak/scope=profile

Set a friendly login button:

sudo /opt/thinlinc/bin/tl-config '/webaccess/oidc/keycloak/button_text=Sign in with Keycloak'

Step 26 - Verify the ThinLinc OIDC Configuration

Run:

sudo /opt/thinlinc/bin/tl-config -Ra /webaccess/oidc/keycloak

Expected output:

button_text = Sign in with Keycloak
scope = profile
discovery_url = https://your-keycloak-domain/realms/thinlinc/.well-known/openid-configuration
client_secret_path = /opt/thinlinc/etc/tlwebaccess/keycloak_secret.txt
client_id = thinlinc-webaccess
username_claim = preferred_username

The important parameters are:

username_claim
discovery_url
client_id
client_secret_path

Step 27 - Restart ThinLinc Web Access

Restart:

sudo systemctl restart tlwebaccess

Check status:

sudo systemctl status tlwebaccess --no-pager

Expected:

Active: active (running)

Part V - Test Keycloak Authentication

Step 28 - Open ThinLinc Web Access

Open a private or incognito browser window and browse to https://thinlinc.linuxbuz.com/.

Click on Sign in with Keycloak, and the browser should redirect to the Keycloak URL https://your-keycloak-domain/realms/thinlinc/.

Enter the Keycloak credentials below and click Sign in.

Username: thinadm
Password: <Keycloak password>

Keycloak authenticates the user and redirects the browser back to https://thinlinc.linuxbuz.com/.

ThinLinc receives:

preferred_username=thinadm

It then resolves:

getent passwd thinadm

If the account exists and PAM permits access, ThinLinc starts the remote desktop session.


Part VI - Optional: Disable ThinLinc Password Login

After Keycloak authentication works reliably, you can disable the normal ThinLinc Web Access password form.

Run:

sudo /opt/thinlinc/bin/tl-config /webaccess/login/password=false

Restart:

sudo systemctl restart tlwebaccess

To restore password login later:

sudo /opt/thinlinc/bin/tl-config /webaccess/login/password=true

Restart again:

sudo systemctl restart tlwebaccess

Part VII - Troubleshooting

External Authentication Failed, Try Again

If ThinLinc displays:

External authentication failed, try again.

first verify the OIDC discovery endpoint:

curl -fsSL 'https://your-keycloak-domain/realms/thinlinc/.well-known/openid-configuration' | python3 -m json.tool | head -40

Then verify ThinLinc:

sudo /opt/thinlinc/bin/tl-config -Ra /webaccess/oidc/keycloak

Expected:

button_text = Sign in with Keycloak
scope = profile
discovery_url = https://your-keycloak-domain/realms/thinlinc/.well-known/openid-configuration
client_secret_path = /opt/thinlinc/etc/tlwebaccess/keycloak_secret.txt
client_id = thinlinc-webaccess
username_claim = preferred_username

Check the client-secret file:

sudo ls -l /opt/thinlinc/etc/tlwebaccess/keycloak_secret.txt

Expected:

-rw------- 1 root root ...

Check that it is not empty:

sudo wc -c /opt/thinlinc/etc/tlwebaccess/keycloak_secret.txt

If there is any doubt, copy the current secret again from:

Keycloak > Clients > thinlinc-webaccess > Credentials

and recreate the file.


Capture the ThinLinc OIDC Error

Monitor:

sudo tail -f /var/log/tlwebaccess.log

If needed:

sudo journalctl -u tlwebaccess -f

Reproduce the login failure in an incognito browser window.

You can also run:

sudo grep -Ei 'oidc|error|warning|invalid|claim|authentication' /var/log/tlwebaccess.log | tail -30

Common failures include:

invalid_client
Failed to exchange authorization code
preferred_username claim missing
Could not resolve user

Do not share client secrets, authorization codes, access tokens, or ID tokens.


Invalid Client Credentials

Verify:

sudo /opt/thinlinc/bin/tl-config /webaccess/oidc/keycloak/client_id

Expected:

thinlinc-webaccess

In Keycloak, verify:

Client authentication: On

If you regenerate the Keycloak client secret, you must update:

/opt/thinlinc/etc/tlwebaccess/keycloak_secret.txt

Then restart:

sudo systemctl restart tlwebaccess

Keycloak Login Works but ThinLinc Rejects the User

Check:

getent passwd thinadm

Check:

id thinadm

The mapping must be:

preferred_username=thinadm
Linux username=thinadm

If they do not match, ThinLinc cannot map the authenticated identity to the Linux user.


Keycloak Username and Linux Username Are Different

Example:

Keycloak username: john@example.com
Linux username:    john

In that case, preferred_username will not map directly.

Create an administrator-controlled Keycloak claim such as:

linux_username

with:

john

Then configure ThinLinc:

sudo /opt/thinlinc/bin/tl-config /webaccess/oidc/keycloak/username_claim=linux_username

Restart:

sudo systemctl restart tlwebaccess

Do not use a user-editable claim for Linux account mapping.


Sign in With Keycloak Button Does Not Appear

Check:

sudo /opt/thinlinc/bin/tl-config -Ra /webaccess/oidc/keycloak

Verify:

client_id
client_secret_path
discovery_url
username_claim

Restart:

sudo systemctl restart tlwebaccess

Monitor:

sudo journalctl -u tlwebaccess -f

ThinLinc Desktop Does Not Start After Authentication

OIDC authentication and Linux account authorization are separate.

Check:

id thinadm

Check password/account status:

sudo passwd -S thinadm

Review ThinLinc logs:

sudo journalctl -u tlwebaccess -f

Review VSM Server logs:

sudo journalctl -u vsmserver -f

Review VSM Agent logs:

sudo journalctl -u vsmagent -f

Conclusion

You now have a complete Keycloak and ThinLinc OIDC environment for browser-based Linux remote desktop access.

2 Likes