Introduction

Rsyslog is the default syslog daemon on most Linux distributions — processing millions of messages per second with filtering, templating, and forwarding to files, databases, or remote servers. Ansible automates the full centralized logging stack: rsyslog server for log collection, client agents for forwarding, TLS encryption, custom templates, and log rotation.

Deploy Rsyslog Server

---
- name: Deploy Rsyslog central server
  hosts: log_servers
  become: true
  vars:
    rsyslog_listen_port: 514
    rsyslog_tls_port: 6514
    rsyslog_log_base: /var/log/remote
  tasks:
    - name: Install Rsyslog with extras
      ansible.builtin.package:
        name:
          - rsyslog
          - rsyslog-gnutls
          - rsyslog-relp
        state: present

    - name: Create remote log directory
      ansible.builtin.file:
        path: "{{ rsyslog_log_base }}"
        state: directory
        owner: syslog
        group: adm
        mode: '0755'

    - name: Deploy server config
      ansible.builtin.template:
        src: rsyslog-server.conf.j2
        dest: /etc/rsyslog.d/10-remote.conf
        mode: '0644'
      notify: restart rsyslog

    - name: Allow syslog through firewall
      ansible.posix.firewalld:
        port: "{{ item }}"
        permanent: true
        state: enabled
        immediate: true
      loop:
        - "{{ rsyslog_listen_port }}/tcp"
        - "{{ rsyslog_listen_port }}/udp"
        - "{{ rsyslog_tls_port }}/tcp"

    - name: Start Rsyslog
      ansible.builtin.service:
        name: rsyslog
        state: started
        enabled: true

  handlers:
    - name: restart rsyslog
      ansible.builtin.service:
        name: rsyslog
        state: restarted

Server Config Template

# templates/rsyslog-server.conf.j2
# Managed by Ansible — Central syslog server

# UDP reception
module(load="imudp")
input(type="imudp" port="{{ rsyslog_listen_port }}")

# TCP reception
module(load="imtcp")
input(type="imtcp" port="{{ rsyslog_listen_port }}")

{% if rsyslog_tls_enabled | default(false) %}
# TLS reception
module(load="imtcp"
  StreamDriver.Name="gtls"
  StreamDriver.Mode="1"
  StreamDriver.AuthMode="x509/fingerprint")

global(
  DefaultNetstreamDriver="gtls"
  DefaultNetstreamDriverCAFile="/etc/rsyslog.d/ssl/ca.crt"
  DefaultNetstreamDriverCertFile="/etc/rsyslog.d/ssl/server.crt"
  DefaultNetstreamDriverKeyFile="/etc/rsyslog.d/ssl/server.key"
)

input(type="imtcp" port="{{ rsyslog_tls_port }}"
  StreamDriver.Name="gtls"
  StreamDriver.Mode="1"
  StreamDriver.AuthMode="anon")
{% endif %}

# Store logs per host/program
template(name="RemoteLog" type="string"
  string="{{ rsyslog_log_base }}/%HOSTNAME%/%PROGRAMNAME%.log")

template(name="RemoteLogDate" type="string"
  string="{{ rsyslog_log_base }}/%HOSTNAME%/%$YEAR%-%$MONTH%-%$DAY%/%PROGRAMNAME%.log")

# JSON template for structured logging
template(name="JsonLog" type="list") {
  constant(value="{")
  constant(value="\"timestamp\":\"") property(name="timereported" dateFormat="rfc3339")
  constant(value="\",\"host\":\"") property(name="hostname")
  constant(value="\",\"severity\":\"") property(name="syslogseverity-text")
  constant(value="\",\"facility\":\"") property(name="syslogfacility-text")
  constant(value="\",\"program\":\"") property(name="programname")
  constant(value="\",\"message\":\"") property(name="msg" format="json")
  constant(value="\"}\n")
}

# Route remote logs
if $fromhost-ip != '127.0.0.1' then {
  action(type="omfile" dynaFile="RemoteLog"
    FileOwner="syslog" FileGroup="adm" FileCreateMode="0640"
    DirCreateMode="0755" DirOwner="syslog" DirGroup="adm")
  stop
}

Configure Clients

---
- name: Configure Rsyslog clients
  hosts: all:!log_servers
  become: true
  vars:
    rsyslog_server: "{{ groups['log_servers'][0] }}"
    rsyslog_server_port: 514
    rsyslog_protocol: tcp
  tasks:
    - name: Install Rsyslog
      ansible.builtin.package:
        name: rsyslog
        state: present

    - name: Deploy forwarding config
      ansible.builtin.template:
        src: rsyslog-client.conf.j2
        dest: /etc/rsyslog.d/50-forward.conf
        mode: '0644'
      notify: restart rsyslog

    - name: Start Rsyslog
      ansible.builtin.service:
        name: rsyslog
        state: started
        enabled: true

  handlers:
    - name: restart rsyslog
      ansible.builtin.service:
        name: rsyslog
        state: restarted
# templates/rsyslog-client.conf.j2
# Forward all logs to central server

# Queue for reliability
$ActionQueueType LinkedList
$ActionQueueFileName fwdRule1
$ActionResumeRetryCount -1
$ActionQueueSaveOnShutdown on
$ActionQueueMaxDiskSpace 1g

{% if rsyslog_protocol == 'tcp' %}
# TCP forwarding
*.* @@{{ rsyslog_server }}:{{ rsyslog_server_port }}
{% else %}
# UDP forwarding
*.* @{{ rsyslog_server }}:{{ rsyslog_server_port }}
{% endif %}

Custom Filtering Rules

- name: Deploy custom filter rules
  ansible.builtin.copy:
    dest: /etc/rsyslog.d/20-filters.conf
    content: |
      # Drop noisy messages
      :msg, contains, "CRON" stop
      
      # Route auth logs separately
      auth,authpriv.* /var/log/auth.log
      
      # Emergency messages to all terminals
      *.emerg :omusrmsg:*
      
      # High-severity to separate file
      *.err;*.crit;*.alert;*.emerg /var/log/critical.log
      
      # Application-specific routing
      if $programname == 'myapp' then {
        action(type="omfile" file="/var/log/myapp/app.log")
        stop
      }
    mode: '0644'
  notify: restart rsyslog

Logrotate Integration

- name: Configure logrotate for remote logs
  ansible.builtin.copy:
    dest: /etc/logrotate.d/remote-syslog
    content: |
      {{ rsyslog_log_base }}/*/*.log {
          daily
          rotate 30
          compress
          delaycompress
          missingok
          notifempty
          sharedscripts
          postrotate
              /usr/lib/rsyslog/rsyslog-rotate
          endscript
      }
    mode: '0644'

Performance Tuning

- name: Deploy high-volume config
  ansible.builtin.copy:
    dest: /etc/rsyslog.d/01-performance.conf
    content: |
      # High-volume tuning
      global(
        workDirectory="/var/spool/rsyslog"
        maxMessageSize="32k"
      )
      
      main_queue(
        queue.type="LinkedList"
        queue.size="500000"
        queue.dequeueBatchSize="1000"
        queue.workerThreads="4"
        queue.workerThreadMinimumMessages="10000"
        queue.maxDiskSpace="2g"
        queue.saveOnShutdown="on"
      )
    mode: '0644'
  notify: restart rsyslog

Health Check

- name: Check Rsyslog status
  ansible.builtin.command: rsyslogd -v
  register: rsyslog_version
  changed_when: false

- name: Test log forwarding
  ansible.builtin.command: logger -t ansible-test "Test message from {{ inventory_hostname }}"
  changed_when: true

- name: Verify log arrived on server
  ansible.builtin.command: "grep 'ansible-test' {{ rsyslog_log_base }}/{{ inventory_hostname }}/ansible-test.log"
  delegate_to: "{{ groups['log_servers'][0] }}"
  register: log_check
  changed_when: false
  ignore_errors: true

Troubleshooting

Check Config Syntax

- name: Validate rsyslog config
  ansible.builtin.command: rsyslogd -N1
  register: config_check
  changed_when: false

Conclusion

Rsyslog is the workhorse of Linux logging — Ansible automates the central server (TCP/UDP/TLS listeners, per-host directory structure, JSON templates) and client forwarding (reliable queues, protocol selection). Use filtering rules for noise reduction, logrotate for disk management, and performance tuning for high-volume environments. Centralized logging as code means every server forwards logs consistently from day one.