DEV Community

Andrew
Andrew

Posted on

Changing a FreeRADIUS Client Secret on Ubuntu and Verifying It with radtest

Ubuntu's FreeRADIUS package ships with a localhost client whose shared secret
is testing123. Here you'll change that secret, restart the server, and confirm
with radtest that FreeRADIUS really uses the new value.

For the editing part I'll use RadiusDeck, a small open-source UI that edits
clients.conf directly, with no SQL database involved. If you also need user management or accounting,
daloRADIUS covers that.

I tested everything below on Ubuntu 22.04.2 with the packaged FreeRADIUS 3.0.26,
RadiusDeck 0.1.0 and Python 3.12.15. The radtest output is from that run.

Before you start

You'll need a test Ubuntu machine with sudo, Git, and a browser on the same
machine. The steps edit /etc/freeradius/3.0/ and restart the system service,
so take a VM snapshot first if the machine matters to you.

Ubuntu 24.04 and 26.04 still package FreeRADIUS 3.x, so the paths and the
freerad group should be the same there.

All passwords and secrets here are throwaway lab values. The web UI will listen
on 127.0.0.1:18080 with no login, which is fine on loopback and nowhere else.

Install FreeRADIUS

sudo apt install freeradius freeradius-utils python3-venv
Enter fullscreen mode Exit fullscreen mode

The package starts the freeradius service right away, with its configuration
in /etc/freeradius/3.0/. freeradius-utils gives you radtest.

Install RadiusDeck

RadiusDeck needs Python 3.12 or newer. Ubuntu 22.04 ships 3.10. My first
install attempt there failed with:

ERROR: Package 'radiusdeck' requires a different Python: 3.10.12 not in '>=3.12'
Enter fullscreen mode Exit fullscreen mode

What I did instead: bootstrap uv with the system Python, then let uv create a
separate Python 3.12 environment for RadiusDeck:

git clone --branch v0.1.0 --depth 1 https://github.com/radiusdeck/radiusdeck.git radiusdeck-demo
cd radiusdeck-demo
python3 -m venv .uv-bootstrap
.uv-bootstrap/bin/python -m pip install uv
.uv-bootstrap/bin/uv venv --python 3.12 --seed .venv312
.venv312/bin/python -m pip install .
Enter fullscreen mode Exit fullscreen mode

It looks a bit roundabout. I still prefer it to adding a third-party PPA such as
deadsnakes: Ubuntu's own python3 stays untouched. If 3.12 isn't installed,
uv downloads a managed interpreter just for this environment
(uv docs). The --seed
option puts pip into the new environment.

On Ubuntu 24.04 and later, the system python3 is already 3.12 or newer, so a
plain venv should be enough.

git clone --branch v0.1.0 --depth 1 https://github.com/radiusdeck/radiusdeck.git radiusdeck-demo
cd radiusdeck-demo
python3 -m venv .venv312
.venv312/bin/python -m pip install .
Enter fullscreen mode Exit fullscreen mode

Let your user write clients.conf

sudo usermod -a -G freerad "$USER"
Enter fullscreen mode Exit fullscreen mode

Log out and back in (or reconnect over SSH) so the new group applies, then check:

id
Enter fullscreen mode Exit fullscreen mode

freerad should be in the list. On my machine clients.conf was owned by
freerad:freerad with mode -rw-r-----, so the group could read it but not
save it. One more change fixes that:

sudo chmod g+w /etc/freeradius/3.0/clients.conf
Enter fullscreen mode Exit fullscreen mode

Get a working baseline

Two different credentials are involved. They're easy to mix up:

Credential Initial value Configured in
Password of user bob hello mods-config/files/authorize
Shared secret of client 127.0.0.1 testing123 clients.conf

The client is the device that sends RADIUS requests. Here radtest plays that
device. RadiusDeck manages the client, bob stays in FreeRADIUS's own files.

Open the users file. /etc/freeradius/3.0/users is a symlink to it:

sudoedit /etc/freeradius/3.0/mods-config/files/authorize
Enter fullscreen mode Exit fullscreen mode

Ubuntu's copy has a commented-out bob example. Uncomment both lines:

bob Cleartext-Password := "hello"
    Reply-Message := "Hello, %{User-Name}"
Enter fullscreen mode Exit fullscreen mode

If the example isn't there, add these lines near the top, above any DEFAULT
entries.

Also check that clients.conf still has the stock localhost client with
testing123. If someone already changed the secret, use the real one below.
Don't add a second client for 127.0.0.1.

In terminal A, validate the configuration:

sudo freeradius -C
Enter fullscreen mode Exit fullscreen mode

If it reports no errors, restart the service and send the first request:

sudo systemctl restart freeradius
radtest bob hello 127.0.0.1 0 testing123
Enter fullscreen mode Exit fullscreen mode

With no port in the server address, radtest sends to UDP 1812. The 0 is the
NAS-Port attribute (radtest manual).

You should see:

Sent Access-Request Id 72 from 0.0.0.0:48371 to 127.0.0.1:1812 length 73
    User-Name = "bob"
    User-Password = "hello"
    NAS-IP-Address = 127.0.1.1
    NAS-Port = 0
    Cleartext-Password = "hello"
Received Access-Accept Id 72 from 127.0.0.1:1812 to 127.0.0.1:48371 length 50
    Message-Authenticator = 0x1858ce772b854342eb35e3c222306682
    Reply-Message = "Hello, bob"
Enter fullscreen mode Exit fullscreen mode

Don't move on without Access-Accept.

Point RadiusDeck at the system clients.conf

In terminal B, from the radiusdeck-demo checkout:

APP_RADIUS_CLIENTS_PATH=/etc/freeradius/3.0/clients.conf \
APP_BACKUP_ENABLED=true \
APP_BACKUP_DIR="$PWD/.system-demo-backups" \
.venv312/bin/python -m radiusdeck.cli.main serve \
  --host 127.0.0.1 --port 18080
Enter fullscreen mode Exit fullscreen mode

Open http://127.0.0.1:18080 and go to Clients. You're looking at the
server's real clients.conf now. Backups go to .system-demo-backups in the
checkout. RadiusDeck creates that directory with mode 0700 and the files with
0600, which matters, since every backup contains your secrets.

Change the secret, then apply it

Find the client with IP 127.0.0.1, normally named localhost. Click Edit,
enter new_secret in Shared secret, and click Save Changes.

The file has changed. The running server still holds testing123 in memory and
keeps using it until it restarts. That gap is where most "I changed it and
nothing happened" moments come from.

Validate before restarting:

sudo freeradius -C
sudo systemctl restart freeradius
radtest bob hello 127.0.0.1 0 new_secret
Enter fullscreen mode Exit fullscreen mode

You should see:

Received Access-Accept Id 54 from 127.0.0.1:1812 to 127.0.0.1:37652 length 50
    Message-Authenticator = 0xeca826a457d5d0280471925dd9b891b3
    Reply-Message = "Hello, bob"
Enter fullscreen mode Exit fullscreen mode

bob and hello didn't change. Only the secret between the client and the
server did. The running server accepted the new one.

If something fails

Symptom What to check
requires a different Python: 3.10.12 You're installing with the system Python. Use .venv312/bin/python.
RadiusDeck can't read clients.conf Run id in the terminal that starts RadiusDeck. No freerad means that session started before usermod.
The client list loads, but saving fails Group write is missing on clients.conf. Repeat the chmod g+w.
The page doesn't open RadiusDeck listens on loopback only. Open the URL on the same machine and keep terminal B running.
new_secret gets no answer Restart after saving, then make sure you edited the client with IP 127.0.0.1.

If the service won't come back after a restart, its journal is the first place
to look. I didn't need it in my run:

sudo journalctl -u freeradius -n 50 --no-pager
Enter fullscreen mode Exit fullscreen mode

Cleaning up

Stop RadiusDeck with Ctrl+C in terminal B. FreeRADIUS keeps running with
bob and new_secret, so remove the test user and set a real secret before the
machine does anything else.

If an edit goes wrong, RadiusDeck saved the previous clients.conf before
writing. Backups → Restore latest puts it back. I didn't test the restore
here.

On a real access point or VPN gateway, the device needs the new secret too.
Until both sides match, nobody behind that device can authenticate, so pick a
quiet moment for the switch.

Top comments (0)