# Managing FreeRADIUS Certificates

This guide explains how to manage the certificates used by **FreeRADIUS** for PEAP, EAP-TTLS, and EAP-TLS.

These are separate from the Apache certificates that protect browser access to daloRADIUS. For the web interface, see [Enabling HTTPS (SSL/TLS) for daloRADIUS](ssl-config.md).

## Before you start

Choose the path that matches your situation:

- **You already have a CA certificate and a server certificate:** follow the Docker or manual-install steps and skip the certificate-generation section.
- **You only need a lab setup:** generate example certificates with the FreeRADIUS templates, then install only the runtime files.
- **You use EAP-TLS:** in addition to the server certificate, plan how client certificates will be issued and revoked. The example `client.pem` is only a test certificate, not a production enrollment process.

Before changing a working server, record the current EAP configuration and back up the current certificate directory. Run all Docker commands from the directory containing `docker-compose.yml`.

## Certificate files and paths

The active paths depend on the installation method:

| Installation | Common certificate directory | EAP configuration |
|---|---|---|
| daloRADIUS Docker stack | `/etc/freeradius/certs` | `/etc/freeradius/mods-available/eap` |
| Debian or Ubuntu package | `/etc/freeradius/3.0/certs` | `/etc/freeradius/3.0/mods-available/eap` |
| RHEL, Rocky Linux, AlmaLinux, or CentOS package | `/etc/raddb/certs` | `/etc/raddb/mods-available/eap` |

Common files include:

| File | Purpose |
|---|---|
| `ca.pem` | CA certificate used to validate the configured certificate chain |
| `ca.der` | The same CA certificate in DER format, useful for some clients |
| `server.pem` | Server certificate; the FreeRADIUS templates also include its encrypted private key in this file |
| `client.pem` | Example EAP-TLS client certificate generated by the test templates |
| `dh`, `random` | Required only when the active EAP configuration references `dh_file` or `random_file` |

For PEAP and EAP-TTLS, clients must validate the FreeRADIUS server certificate and trust its issuing CA. EAP-TLS additionally requires FreeRADIUS to validate client certificates against the intended client CA.

For example, an organization using `radius.example.org` might have:

- a CA certificate named `Example Network CA`, stored as `ca.pem`;
- a server certificate whose Subject Alternative Name (SAN) contains `DNS:radius.example.org`, stored with its private key as `server.pem`;
- client profiles configured to trust `Example Network CA` and accept only `radius.example.org` as the authentication server.

PEM files are Base64 text and are commonly used by FreeRADIUS. DER contains the same certificate in a binary format accepted by some client platforms. If a client requires DER and only `ca.pem` is available, convert the public CA certificate with:

```bash
openssl x509 -in ca.pem -outform DER -out ca.der
```

Before installation, inspect the certificate identity and validity period:

```bash
openssl x509 -in server.pem -noout \
  -subject -issuer -dates -ext subjectAltName
openssl verify -CAfile ca.pem server.pem
```

The verification example assumes that `ca.pem` directly signed the server certificate. When intermediate CAs are used, include them in the verification and server chain according to your CA's instructions. Replace `server.pem` with the certificate file when the private key is stored separately. Check that the SAN contains the name that client devices will be configured to expect.

Never distribute a server or CA private key. Do not configure clients to skip server certificate validation.

## Docker install

FreeRADIUS runs in the `radius` container. Certificates belong to that container, not to `radius-web`.

Keep certificates on the host and bind-mount them read-only. Files edited only inside a running container disappear when it is recreated.

The usual Docker workflow is:

1. put the runtime files in a host directory;
2. update EAP only if the filenames or key password differ from the defaults;
3. add read-only mounts to Compose;
4. validate the configuration;
5. recreate the `radius` service and test one client.

### 1. Prepare a runtime certificate directory

The host directory can use any organization scheme. For example, keep the year on the host while retaining the standard paths inside the container:

```bash
mkdir -p ./data/freeradius-certs/2026
```

Place the runtime files in that directory:

```text
./data/freeradius-certs/2026/ca.pem
./data/freeradius-certs/2026/ca.der
./data/freeradius-certs/2026/server.pem
```

`data/` is ignored by this repository, but verify your own source-control and backup rules before placing private material there.

If a separate private key or certificate chain is used instead of the combined `server.pem`, place those files there too and update the EAP paths in the next step.

Typical mappings are:

| Files supplied by your CA | EAP settings to use |
|---|---|
| One `server.pem` containing the certificate and encrypted key | Use `server.pem` for both `private_key_file` and `certificate_file` |
| `server.key` plus `server.crt` | Use `server.key` for `private_key_file` and `server.crt` for `certificate_file` |
| `server.key`, `server.crt`, and intermediate certificates | Build `server-chain.pem` with the server certificate first, followed by the intermediate certificates; do not append the root CA |

For one intermediate CA, the chain file can be assembled with:

```bash
cat server.crt intermediate-ca.pem > server-chain.pem
```

### 2. Configure the EAP module when required

The image defaults normally reference:

```text
private_key_password = whatever
private_key_file = ${certdir}/server.pem
certificate_file = ${certdir}/server.pem
ca_file = ${cadir}/ca.pem
```

The default paths need no change when the host directory is mounted at `/etc/freeradius/certs`. Update the EAP configuration if the filenames differ or the private key password is not `whatever`.

`private_key_password` must be the password that decrypts the server private key. It is not a RADIUS shared secret, database password, or Wi-Fi user password. The value `whatever` matches the bundled test templates; do not assume it matches a certificate supplied by your own CA.

Do not edit the file only inside the running container. Copy it to the host:

```bash
mkdir -p ./data/freeradius-config
docker cp radius:/etc/freeradius/mods-available/eap \
  ./data/freeradius-config/eap
```

Edit `./data/freeradius-config/eap`, then protect it because it can contain the private key password:

```bash
chmod 640 ./data/freeradius-config/eap
```

For a separate key and certificate, settings may look like:

```text
private_key_password = replace_with_the_key_password
private_key_file = ${certdir}/server.key
certificate_file = ${certdir}/server-chain.pem
ca_file = ${cadir}/client-ca.pem
```

For EAP-TLS, `ca_file` defines which CA certificates FreeRADIUS trusts for client authentication. Do not point it at a broad public CA bundle unless the authorization policy deliberately restricts which client certificates are accepted.

### 3. Add read-only mounts

Add the certificate directory to the `radius` service in `docker-compose.yml`:

```yaml
services:
  radius:
    volumes:
      - ./data/freeradius:/data
      - ./data/freeradius-certs/2026:/etc/freeradius/certs:ro
      - radius_logs:/var/log/freeradius
```

If the EAP configuration was customized, add its mount too:

```yaml
      - ./data/freeradius-config/eap:/etc/freeradius/mods-enabled/eap:ro
```

Mounting the host's year directory at the standard container path avoids patching `radiusd.conf` or hard-coding a year into the image. Avoid mounting all of `/etc/freeradius`, because that hides configuration supplied by the image.

### 4. Set permissions

Use a one-off container so ownership matches the `freerad` account in the image:

```bash
docker compose run --rm --no-deps \
  --entrypoint sh \
  -v "$PWD/data/freeradius-certs/2026:/certs" \
  radius -lc '
    chown -R freerad:freerad /certs
    chmod 640 /certs/server.pem
    chmod 644 /certs/ca.pem /certs/ca.der
  '
```

If separate key files are used, keep them at mode `600` or `640` and ensure only the FreeRADIUS service account can read them.

For a customized EAP file, apply equivalent ownership and restrictive permissions:

```bash
docker compose run --rm --no-deps \
  --entrypoint sh \
  -v "$PWD/data/freeradius-config:/config" \
  radius -lc 'chown freerad:freerad /config/eap; chmod 640 /config/eap'
```

### 5. Validate and restart

Render the Compose configuration and validate FreeRADIUS before restarting the service:

```bash
docker compose config --quiet

docker compose run --rm --no-deps \
  --entrypoint freeradius \
  radius -C -l stdout
```

Warnings that SQL or LDAP are ignored can appear in this one-off validation because its dependencies were intentionally not started. Certificate path, permission, or password errors must still be fixed.

Apply the mounts and inspect the service:

```bash
docker compose up -d radius
docker compose ps radius
docker compose logs --tail=100 radius
```

A successful configuration check proves that FreeRADIUS can load the files. Complete validation still requires an EAP authentication from a client configured to verify the expected server name and CA.

On the first test client, confirm all three results before deploying the profile more widely:

- authentication succeeds;
- the client reports the expected server name, such as `radius.example.org`;
- removing the test CA or configuring a wrong server name makes authentication fail.

The negative test confirms that the client is actually validating the server certificate instead of silently accepting any RADIUS server.

### Optional: generate lab certificates from the image templates

The FreeRADIUS templates generate short-lived example certificates for testing. Do not use them as an unattended production PKI.

Copy the tooling into a separate work directory. Keep this directory separate from the runtime mount because it contains the CA private key and generated client private keys:

```bash
mkdir -p ./data/freeradius-cert-work
docker cp radius:/etc/freeradius/certs/. \
  ./data/freeradius-cert-work/
```

Edit the actual template files used by the Makefile:

```text
./data/freeradius-cert-work/ca.cnf
./data/freeradius-cert-work/server.cnf
./data/freeradius-cert-work/client.cnf
```

The tooling reads these exact filenames; copies such as `server.cnf.local` are not used automatically.

If the copied directory already contains generated example certificates, regenerate it only after confirming that it contains no production material:

```bash
docker compose run --rm --no-deps \
  --entrypoint sh \
  -v "$PWD/data/freeradius-cert-work:/cert-work" \
  radius -lc 'cd /cert-work && make destroycerts && sh ./bootstrap'
```

`make destroycerts` deletes generated keys and certificates in that directory. Back up production material and never run it against a production certificate directory.

Copy only the required runtime files out of the work directory. Use a one-off container because the generated private files may not be readable by your host account:

```bash
mkdir -p ./data/freeradius-certs/2026

docker compose run --rm --no-deps \
  --entrypoint sh \
  -v "$PWD/data/freeradius-cert-work:/cert-work:ro" \
  -v "$PWD/data/freeradius-certs/2026:/certs" \
  radius -lc '
    cp /cert-work/ca.pem /cert-work/ca.der /cert-work/server.pem /certs/
    chown freerad:freerad /certs/*
    chmod 640 /certs/server.pem
    chmod 644 /certs/ca.pem /certs/ca.der
  '
```

The `output_password` in `server.cnf` must match `private_key_password` in the EAP configuration. Then run the validation steps above.

## Manual install

Package paths and defaults differ by distribution. Inspect the installed configuration instead of assuming a path.

### 1. Locate the active configuration

On Debian or Ubuntu:

```bash
sudo freeradius -XC
sudo editor /etc/freeradius/3.0/mods-available/eap
```

On RHEL-like systems:

```bash
sudo radiusd -XC
sudo editor /etc/raddb/mods-available/eap
```

A fresh RHEL-like package can fail its first configuration check because the example certificates have not been generated yet. In that case, confirm that the error points to the expected certificate files, generate or install them, and rerun the check.

### 2. Install certificates and configure EAP

Use the distribution's certificate directory:

```text
/etc/freeradius/3.0/certs   # Debian or Ubuntu
/etc/raddb/certs            # RHEL-like systems
```

Set `private_key_file`, `certificate_file`, `ca_file`, and, when applicable, `private_key_password` in the active EAP module. Use the same certificate-chain and EAP-TLS trust restrictions described in the Docker section.

The following examples assume that the new files are temporarily stored in `/root/radius-certs`. Replace that source path and filenames with your own. `install` copies each file while applying its final owner and mode.

Debian or Ubuntu example:

```bash
sudo install -o freerad -g freerad -m 600 \
  /root/radius-certs/server.pem \
  /etc/freeradius/3.0/certs/server.pem
sudo install -o root -g root -m 644 \
  /root/radius-certs/ca.pem \
  /etc/freeradius/3.0/certs/ca.pem
```

RHEL-like example:

```bash
sudo install -o radiusd -g radiusd -m 600 \
  /root/radius-certs/server.pem \
  /etc/raddb/certs/server.pem
sudo install -o root -g root -m 644 \
  /root/radius-certs/ca.pem \
  /etc/raddb/certs/ca.pem
sudo restorecon -RFv /etc/raddb/certs
```

Install `ca.der`, a separate private key, and a certificate-chain file in the same way when they are used. Public certificates can normally use mode `644`; private keys should use `600` and be owned by the FreeRADIUS service account. Adjust ownership when the service account reported by `freeradius -XC` or `radiusd -XC` differs.

### 3. Optional: generate lab certificates from package templates

Back up the certificate directory first. Edit `ca.cnf`, `server.cnf`, and `client.cnf` in place; `.local` copies are not read automatically by the supplied Makefile.

On Debian or Ubuntu, `bootstrap` may not be executable, so invoke it through `sh`:

```bash
cd /etc/freeradius/3.0/certs
sudo make destroycerts
sudo sh ./bootstrap
```

On RHEL-like systems:

```bash
cd /etc/raddb/certs
sudo make destroycerts
sudo ./bootstrap
```

These commands delete and regenerate the example keys and certificates. Do not use them against production material. Review ownership and permissions afterward; the CA private key and generated client private keys must remain protected.

### 4. Validate and restart

Debian or Ubuntu:

```bash
sudo freeradius -XC
sudo systemctl restart freeradius.service
sudo journalctl -u freeradius.service -n 100 --no-pager
```

RHEL-like systems:

```bash
sudo radiusd -XC
sudo systemctl restart radiusd.service
sudo journalctl -u radiusd.service -n 100 --no-pager
```

## Client distribution

Distribute only the CA certificate needed for clients to validate the FreeRADIUS server:

```text
ca.pem or ca.der
```

Configure clients or MDM profiles with the expected authentication-server name (CN or SAN) as well as the CA. Do not distribute `server.pem`, `server.key`, `ca.key`, private client keys, or the EAP private key password.

For the `radius.example.org` example, a client profile should contain the CA certificate and `radius.example.org` as the allowed server name. A user should not be asked to approve a different or untrusted certificate during normal connection.

## Troubleshooting

| Symptom | Likely cause | Action |
|---|---|---|
| FreeRADIUS fails its configuration check | Wrong path, unreadable file, invalid chain, or wrong key password | Run `freeradius -XC`, `radiusd -XC`, or the Docker validation command and fix the first certificate error. |
| Clients reject the server certificate | Missing CA trust or unexpected server identity | Install the intended CA and configure the expected CN or SAN on clients. |
| Docker changes disappear after recreation | Files were changed only inside the container | Keep them on the host and use read-only mounts. |
| Permission denied while reading the private key | Ownership, mode, or SELinux label is wrong | Verify the service account, permissions, and RHEL-like SELinux context. |
| EAP-TLS accepts or rejects the wrong client certificates | `ca_file` or certificate authorization policy is too broad or incorrect | Restrict the client CA and verify the EAP-TLS authorization policy in FreeRADIUS debug output. |

## Security notes

- Keep server, CA, and client private keys out of source control and container images.
- Keep the CA private key offline when possible; the FreeRADIUS runtime does not need it.
- Prefer read-only runtime mounts and restrictive permissions.
- Treat a private key password stored in the EAP configuration as a secret.
- Use the bundled templates only for development and controlled lab testing.
- Test renewal procedures before the current certificate expires.
