📡 Centralized Logging

Logging & Log Management Series: Part 1 — Basics  |  Part 2 — journald  |  Part 3 — logrotate  |  Part 4 — Centralized Logging

Why Centralize Logs?

When you manage more than a handful of servers, checking logs on each one individually does not scale. Centralized logging collects logs from all your systems into one place — making it possible to search across all servers at once, correlate events, set up alerts, and keep logs safe even if a server is compromised or its disk fills up.

This page covers the practical approach for a Linux shop — using rsyslog to forward logs to a central server, organizing them by hostname, securing the transport, and knowing when to consider more advanced tools like the ELK stack or Graylog.

Centralized Logging Architecture

[ web01 ] [ web02 ] [ db01 ] [ app01 ] | | | | | rsyslog | rsyslog | rsyslog | rsyslog | forwarding | forwarding | forwarding | forwarding | | | | +------+--------+------+--------+-----+-------+ | | | +--------------------+-----------------+ | [ logserver (TCP 514) ] /var/log/remote/web01/ /var/log/remote/web02/ /var/log/remote/db01/

Examples

1
Configure the Log Server — Receive Logs

Set up the central log server to accept incoming syslog messages:

# On the LOG SERVER

# Create /etc/rsyslog.d/00-server.conf
sudo tee /etc/rsyslog.d/00-server.conf << 'EOF'
# Enable TCP syslog reception on port 514
module(load="imtcp")
input(type="imtcp" port="514")

# Enable UDP syslog reception (for legacy clients)
module(load="imudp")
input(type="imudp" port="514")

# Template: store logs in per-host directories
template(name="PerHostLog" type="string"
    string="/var/log/remote/%HOSTNAME%/%PROGRAMNAME%.log")

# Apply template to all remote messages
if $fromhost-ip != "127.0.0.1" then {
    action(type="omfile" dynaFile="PerHostLog")
    stop
}
EOF

# Create the remote log directory
sudo mkdir -p /var/log/remote
sudo chmod 755 /var/log/remote

# Open firewall for syslog
sudo firewall-cmd --permanent --add-port=514/tcp
sudo firewall-cmd --permanent --add-port=514/udp
sudo firewall-cmd --reload

# Restart rsyslog
sudo systemctl restart rsyslog

# Verify it is listening
sudo ss -tulpn | grep 514
TCP vs UDP for syslog: UDP port 514 is the traditional syslog transport — fast but unreliable (messages can be lost). TCP port 514 provides reliable delivery with acknowledgment. Use TCP for important logs. Keep UDP for legacy devices that only support UDP syslog (network switches, appliances).
2
Configure Clients to Forward Logs
# On each CLIENT SERVER

# Create /etc/rsyslog.d/99-forward.conf
sudo tee /etc/rsyslog.d/99-forward.conf << 'EOF'
# Forward all logs to central log server via TCP
*.* action(type="omfwd"
           target="logserver.example.com"
           port="514"
           protocol="tcp"
           action.resumeRetryCount="100"
           queue.type="linkedList"
           queue.size="10000"
           queue.filename="fwd-queue"
           queue.saveOnShutdown="on")
EOF

# Restart rsyslog on the client
sudo systemctl restart rsyslog

# Test -- send a test message and check the server
logger -t test "Forwarding test from $(hostname)"

# On the log server, verify receipt:
sudo tail /var/log/remote/web01/test.log
💡 The queue is essential for reliability. The queue.* settings create a disk-backed queue on the client. If the log server is unreachable, messages are queued locally and forwarded when the connection is restored — no log messages are lost during network interruptions.
3
Secure Log Forwarding with TLS

Plain TCP syslog is unencrypted — anyone on the network can read it. Use TLS for sensitive environments:

# Install TLS support
sudo dnf install -y rsyslog-gnutls    # RHEL
sudo apt install -y rsyslog-gnutls    # Debian

# Generate certificates (on the log server)
sudo openssl req -x509 -newkey rsa:4096 \
    -keyout /etc/rsyslog-keys/server.key \
    -out /etc/rsyslog-keys/server.crt \
    -days 3650 -nodes \
    -subj "/CN=logserver.example.com"

# Log SERVER TLS config (/etc/rsyslog.d/00-tls-server.conf)
sudo tee /etc/rsyslog.d/00-tls-server.conf << 'EOF'
module(load="imtcp"
       StreamDriver.Name="gtls"
       StreamDriver.Mode="1"
       StreamDriver.Authmode="anon")

global(
    DefaultNetstreamDriver="gtls"
    DefaultNetstreamDriverCAFile="/etc/rsyslog-keys/server.crt"
    DefaultNetstreamDriverCertFile="/etc/rsyslog-keys/server.crt"
    DefaultNetstreamDriverKeyFile="/etc/rsyslog-keys/server.key"
)

input(type="imtcp" port="6514")
EOF

# Log CLIENT TLS config (/etc/rsyslog.d/99-tls-forward.conf)
sudo tee /etc/rsyslog.d/99-tls-forward.conf << 'EOF'
global(
    DefaultNetstreamDriver="gtls"
    DefaultNetstreamDriverCAFile="/etc/rsyslog-keys/server.crt"
)

*.* action(type="omfwd"
           target="logserver.example.com"
           port="6514"
           protocol="tcp"
           StreamDriver="gtls"
           StreamDriverMode="1"
           StreamDriverAuthMode="anon")
EOF

sudo systemctl restart rsyslog
Port 6514 is the IANA-assigned port for syslog over TLS. Port 514 is plain syslog. Using different ports makes it easy to see at a glance whether a connection is encrypted.
4
Organize Remote Logs by Hostname and Program
# Advanced template -- organize by hostname, date, and program
sudo tee /etc/rsyslog.d/00-templates.conf << 'EOF'
# Store per host, per program
template(name="PerHostProgram" type="string"
    string="/var/log/remote/%HOSTNAME%/%PROGRAMNAME%.log")

# Store per host, per day (date-based files)
template(name="PerHostDaily" type="string"
    string="/var/log/remote/%HOSTNAME%/%$YEAR%-%$MONTH%-%$DAY%.log")

# Combined format with hostname in message
template(name="FullMsg" type="string"
    string="%TIMESTAMP% %HOSTNAME% %syslogtag%%msg%\n")

# Route by facility
if $syslogfacility-text == 'authpriv' then {
    action(type="omfile"
           dynaFile="PerHostProgram"
           template="FullMsg")
    stop
}

# Everything else by host and program
*.* action(type="omfile" dynaFile="PerHostProgram")
EOF

sudo systemctl restart rsyslog

# Set up logrotate for remote logs
sudo tee /etc/logrotate.d/remote-logs << 'EOF'
/var/log/remote/*/*.log {
    daily
    rotate 90
    compress
    delaycompress
    missingok
    notifempty
    dateext
    sharedscripts
    postrotate
        systemctl kill -s HUP rsyslog.service 2>/dev/null || true
    endscript
}
EOF
💡 Always set up logrotate on the log server. Without rotation, /var/log/remote/ will fill the disk as fast as all your clients can generate logs combined. With 10 servers each generating 100MB/day, that's 1GB/day unrotated.
5
Forward Only Security-Relevant Logs

Rather than forwarding everything, selectively forward the logs that matter most for security and compliance:

# Forward only auth/security messages (bandwidth efficient)
sudo tee /etc/rsyslog.d/99-security-forward.conf << 'EOF'
# Forward auth messages
authpriv.* action(type="omfwd"
                  target="logserver.example.com"
                  port="514"
                  protocol="tcp")

# Forward kernel messages
kern.* action(type="omfwd"
              target="logserver.example.com"
              port="514"
              protocol="tcp")

# Forward errors and above from everything
*.err action(type="omfwd"
             target="logserver.example.com"
             port="514"
             protocol="tcp")

# Forward cron (for audit trail)
cron.* action(type="omfwd"
              target="logserver.example.com"
              port="514"
              protocol="tcp")
EOF

sudo systemctl restart rsyslog
What to always forward centrally:
6
Search Across All Servers at Once

With logs centralized by hostname, searching across your entire infrastructure is straightforward:

# Search for failed SSH logins across ALL servers
sudo grep -r "Failed password" /var/log/remote/

# Which servers had the most failures?
sudo grep -r "Failed password" /var/log/remote/ | \
    awk -F/ '{print $5}' | sort | uniq -c | sort -rn

# Find all sudo commands run today across all servers
sudo grep -r "$(date +%b\ %e)" /var/log/remote/*/sudo.log 2>/dev/null

# Find all errors in the last hour across all servers
sudo find /var/log/remote -name "*.log" -newer /tmp/1hour -exec \
    grep -l "error\|Error\|ERROR" {} \;

# Which server generated a specific event?
sudo grep -r "Out of memory" /var/log/remote/*/kern.log

# Create a 1-hour marker file for the search above
touch -t $(date -d "1 hour ago" +%Y%m%d%H%M) /tmp/1hour
💡 grep -r is your cross-server search. With logs organized as /var/log/remote/HOSTNAME/PROGRAM.log, a recursive grep instantly searches your entire infrastructure. For larger environments consider a log indexer like Elasticsearch or Loki — but for dozens of servers, grep across a well-organized directory tree is surprisingly effective.
7
Forwarding journald to rsyslog to Central Server

On systemd systems, make journald feed rsyslog so journal entries are also forwarded centrally:

# Verify journald is forwarding to syslog
grep ForwardToSyslog /etc/systemd/journald.conf

# Enable if not set
sudo vi /etc/systemd/journald.conf
# Set: ForwardToSyslog=yes
sudo systemctl restart systemd-journald

# Now rsyslog receives journal entries and forwards them
# Verify the chain is working:
logger -t chaintest "Testing journal to rsyslog to central"

# Check locally
journalctl -t chaintest -n 3

# Check on log server
sudo tail /var/log/remote/web01/chaintest.log

# Alternative: forward journal directly without rsyslog
# systemd-journal-remote on the server side
sudo dnf install -y systemd-journal-remote
sudo systemctl enable --now systemd-journal-remote.socket
Two approaches to journal forwarding: ForwardToSyslog=yes in journald.conf sends journal entries to rsyslog, which then handles forwarding — simple and works with your existing rsyslog setup. systemd-journal-remote forwards the native journal format, preserving all metadata, but requires more setup. For most environments, the rsyslog approach is simpler.
8
When to Consider ELK / Loki / Graylog

rsyslog centralization works well up to a point. Here is how to know when you need a dedicated log management platform:

# Check how much log data you are generating daily
sudo du -sh /var/log/remote/
sudo find /var/log/remote -name "*.log" -mtime -1 -exec du -sh {} \; | \
    awk '{total += $1} END {print "Daily volume: " total "K"}'

# Count log events per second across all servers
sudo tail -f /var/log/remote/*/*.log | pv -l -i 5 > /dev/null
SituationRecommendation
Under 20 servers, <10GB/dayrsyslog centralization — simple and effective
Need full-text search UIGraylog (open source, self-hosted)
Kubernetes/container logsGrafana Loki — designed for this use case
Complex analytics, dashboardsELK Stack (Elasticsearch + Logstash + Kibana)
Already using GrafanaGrafana Loki — integrates natively
Compliance/SIEM needsCommercial SIEM or Wazuh (open source)
Cloud environmentCloudWatch (AWS), Cloud Logging (GCP), Azure Monitor
Start simple. rsyslog centralization with grep-based searching handles most small-to-medium Linux environments perfectly well. The operational overhead of ELK or Graylog is significant — only add that complexity when you genuinely need the search UI, alerting, or analytics that rsyslog alone cannot provide. Many shops run rsyslog centralization for years before needing anything more.

Quick Reference

rsyslog Forwarding Config Summary

PurposeConfig snippet
Forward all via TCP*.* action(type="omfwd" target="logserver" port="514" protocol="tcp")
Forward auth onlyauthpriv.* action(type="omfwd" target="logserver" port="514" protocol="tcp")
Forward errors and above*.err action(type="omfwd" target="logserver" port="514" protocol="tcp")
Receive TCP on servermodule(load="imtcp") input(type="imtcp" port="514")
Store by hostnametemplate(name="T" type="string" string="/var/log/remote/%HOSTNAME%/%PROGRAMNAME%.log")

Architecture Comparison

ApproachProsCons
rsyslog forwardingSimple, lightweight, already installedNo UI, grep-based search only
GraylogFull search UI, alerts, dashboardsRequires MongoDB + Elasticsearch
ELK StackPowerful analytics, great for large scaleHeavy resource requirements
Grafana LokiLightweight, integrates with GrafanaLess mature than ELK
WazuhSIEM features, compliance, IDSMore complex setup

← Back to Logging Index ↑ Back to EXPANDED