Configuring systemd-journal-remote on Fedora Server: Centralized Logging over HTTPS mTLS

Fedora tutorial - IT technology blog
Fedora tutorial - IT technology blog

2 AM and the Mystery of the Vanished Server Logs

At precisely 2:15 AM, the shrill PagerDuty alarm woke up the entire house. A worker node handling 80 background Celery jobs in our production cluster abruptly went dark. Ping requests failed. SSH timed out. I rushed to the Hetzner web console and triggered a hard reset. The server was back online within 40 seconds, but any clue about what caused the crash had vanished into thin air.

I have been running Fedora on my workstation for two years thanks to its cutting-edge kernels and rock-solid dnf package management. Naturally, when provisioning four new worker nodes, Fedora Server 40 was my top choice. To my dismay, running journalctl -b -1 yielded an empty terminal. The server had suffered a kernel panic, forcing an emergency unmount of the Btrfs filesystem. The RAM log buffers never had a chance to flush to the NVMe drive before the power cut. Losing logs in the middle of the night is every on-call engineer’s worst nightmare.

Why Relying Exclusively on Local Logs Is a Recipe for Disaster

By default on Fedora, systemd-journald aggregates all logs across the kernel, systemd services, and Podman containers. Its binary storage format is blazing fast and makes log filtering effortless. However, storing logs strictly on local disks exposes three fatal vulnerabilities:

  • Total data loss during kernel panics: If a server loses power or hard-locks, any logs buffered in RAM that haven’t been flushed to /var/log/journal/ are gone for good.
  • Covered tracks after root compromises: Once attackers gain root access, their first move is typically purging journal files to erase evidence of their intrusion.
  • Operational pain across three or more nodes: Troubleshooting issues means juggling multiple SSH sessions running journalctl -u app.service. This workflow is slow, frustrating, and makes correlating cross-node events virtually impossible.

Comparing Common Log Aggregation Solutions

When addressing centralized logging, system administrators generally weigh three primary approaches:

1. The ELK Stack (Elasticsearch, Logstash, Kibana) or Graylog

The standard enterprise-grade powerhouse. The downside is massive resource overhead: the JVM alone frequently eats 4–8 GB of RAM. For modest clusters of 3 to 8 nodes with 2–4 GB VPS instances, running ELK is tremendous overkill.

2. Traditional rsyslog

Rsyslog is lightweight (often under 30 MB of RAM) and widely understood. Unfortunately, it transmits plain text by default, requiring stunnel wrappers to be secure. Worse, it flattens structured log entries into unstructured strings, completely stripping out valuable metadata fields like _SYSTEMD_UNIT, _PID, and _COMM.

3. systemd-journal-remote and systemd-journal-upload over HTTPS

The native solution already built into systemd. It consumes only 15–25 MB of RAM per host while preserving 100% of binary metadata and enforcing mutual TLS (mTLS) with x509 certificates. Logs are streamed in real time down to the millisecond. Even if a worker node crashes abruptly, entries generated just half a second earlier are already safely stored on your remote log server.

Deploying systemd-journal-remote on Fedora Server over HTTPS

Our lab topology consists of two hosts:

  • Log Server (Fedora Server 40): Listening on HTTPS port 19532, IP 192.168.10.50.
  • Client Node (Fedora Server 40): Streaming logs continuously, IP 192.168.10.51.

Step 1: Install the Required Packages

Run the following command on both the Log Server and the Client Node:

sudo dnf install -y systemd-journal-remote openssl

Step 2: Generate Digital Certificates (mTLS) for HTTPS

To prevent unauthorized hosts from forwarding rogue log streams, generate x509 certificates for mutual authentication (mTLS). Run these steps directly on the Log Server:

# Create directory for certificates and private keys
sudo mkdir -p /etc/ssl/journal-remote
cd /etc/ssl/journal-remote

# 1. Generate internal Root CA (valid for 10 years)
sudo openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
  -keyout ca.key -out ca.pem -subj "/CN=Journal-Internal-CA"

# 2. Generate Certificate for Log Server
sudo openssl req -new -nodes -newkey rsa:2048 \
  -keyout server.key -out server.csr -subj "/CN=192.168.10.50"
sudo openssl x509 -req -days 1095 -in server.csr \
  -CA ca.pem -CAkey ca.key -CAcreateserial -out server.pem

# 3. Generate Certificate for Client Node
sudo openssl req -new -nodes -newkey rsa:2048 \
  -keyout client.key -out client.csr -subj "/CN=192.168.10.51"
sudo openssl x509 -req -days 1095 -in client.csr \
  -CA ca.pem -CAkey ca.key -CAcreateserial -out client.pem

# Set secure permissions: restricted to service account
sudo chown -R systemd-journal-remote:systemd-journal-remote /etc/ssl/journal-remote
sudo chmod 600 /etc/ssl/journal-remote/*.key

Transfer ca.pem, client.pem, and client.key to the Client Node using scp:

scp ca.pem client.pem client.key [email protected]:/tmp/

# On the Client Node:
sudo mkdir -p /etc/ssl/journal-remote
sudo mv /tmp/{ca.pem,client.pem,client.key} /etc/ssl/journal-remote/
sudo chown -R systemd-journal-upload:systemd-journal-upload /etc/ssl/journal-remote
sudo chmod 600 /etc/ssl/journal-remote/client.key

Step 3: Configure the Log Server to Receive Inbound Logs

Edit the receiver configuration file on the Log Server:

sudo nano /etc/systemd/journal-remote.conf

Apply the following configuration:

[Remote]
Seal=false
SplitMode=host
ServerKeyFile=/etc/ssl/journal-remote/server.key
ServerCertificateFile=/etc/ssl/journal-remote/server.pem
TrustedCertificateFile=/etc/ssl/journal-remote/ca.pem

Key parameters to note:

  • SplitMode=host: Automatically splits logs into individual files per client hostname/IP under /var/log/journal/remote/.
  • TrustedCertificateFile: Enables mTLS. Only clients presenting certificates signed by our internal CA are permitted to upload logs.

Open the firewall port and enable the socket:

# Open TCP port 19532
sudo firewall-cmd --add-port=19532/tcp --permanent
sudo firewall-cmd --reload

# Enable the socket (systemd spawns the service on incoming connections)
sudo systemctl enable --now systemd-journal-remote.socket

Step 4: Configure the Client Node to Stream Logs

On the Client Node, open /etc/systemd/journal-upload.conf:

sudo nano /etc/systemd/journal-upload.conf

Configure the remote endpoint and client certificates:

[Upload]
URL=https://192.168.10.50:19532
ServerKeyFile=/etc/ssl/journal-remote/client.key
ServerCertificateFile=/etc/ssl/journal-remote/client.pem
TrustedCertificateFile=/etc/ssl/journal-remote/ca.pem

Start the upload service:

sudo systemctl enable --now systemd-journal-upload.service

Verify connection status:

sudo systemctl status systemd-journal-upload.service

If the service reports Active: active (running) alongside lines like Uploaded ... bytes, logs are successfully flowing across the encrypted HTTPS channel.

Step 5: Querying Remote Logs

Return to the Log Server and inspect the destination directory:

ls -lh /var/log/journal/remote/

You should see remote-192.168.10.51.journal. Query it with the familiar speed of binary indexing using the --file flag:

# Follow logs in real time, similar to tail -f
sudo journalctl --file=/var/log/journal/remote/remote-192.168.10.51.journal -f

# Filter specifically for critical errors (Error through Emergency)
sudo journalctl --file=/var/log/journal/remote/remote-192.168.10.51.journal -p err..emerg -n 50

Production Best Practices

While this architecture is both lightweight and resilient, keep these two critical operational considerations in mind:

1. Preventing Disk Exhaustion: Continuous log forwarding can fill the /var partition over time. Create /etc/tmpfiles.d/journal-remote.conf to have systemd automatically prune logs older than 30 days:

# Clean up remote logs older than 30 days
d /var/log/journal/remote 0755 systemd-journal-remote systemd-journal-remote 30d

2. Handling Network Partitions: systemd-journal-upload tracks transmission progress using a persistent cursor. If connectivity between nodes drops intermittently, the client buffers entries locally and catches up automatically once the network recovers, preventing any log loss.

Share: