ClamAV Real-Time Protection Configuration (Linux)

Guide to enable real-time (on-access) antivirus protections on Linux devices that meet the requirements for UW-Madison - IT - Endpoint Management and Security Policy Standards.

It is strongly recommended to use Cisco Secure Endpoint (Cisco Secure Endpoint (AMP) - Overview) on UW-Madison owned devices. When properly configured Cisco Secure Endpoint meets the UW-Madison - IT - Endpoint Management and Security Policy Standards and works well with UW-Madison cybersecurity tools.

RHEL

This guide is based on a clean Rocky Linux 10.1 install using the GlobalProtect 6.2.9-c4 command line client (WiscVPN GlobalProtect (Linux) - Installing, Connecting, and Uninstalling). Most RHEL compatible environments and/or newer client versions should be similar.

Environment & Component Installs

  1. Ensuring updates are applied is recommended
    sudo dnf update -y
  2. Install EPEL repository if not already installed
    sudo dnf install -y epel-release
  3. Install ClamAV packages
    sudo dnf install -y clamav clamd clamav-update clamav-server clamav-scanner-systemd

Configure the Update Service (FreshClam)

  1. Manually run the update to verify it is working and you have current definitions. You can make any needed changes to the config file located at /etc/freshclam.conf
    sudo freshclam
  2. Enable the update service
    sudo systemctl enable --now clamav-freshclam
  3. Verify freshclam is running
    sudo systemctl status clamav-freshclam

Configure the Scanning Daemon (clamd)

  1. Edit the scan config
    sudo nano /etc/clamd.d/scan.conf
    1. Uncomment the following line
      LocalSocket /run/clamd.scan/clamd.sock
  2. Start clamd
    sudo systemctl enable --now clamd@scan
  3. Verify clamd is running
    sudo systemctl status clamd@scan

Enable Real-Time Protection (clamonacc)

ClamAV provides clamonacc for on-access scanning. Because there isn't a default systemd service for it on RHEL-based systems you'll need to configure it manually.

  1. Edit the scan config
    sudo nano /etc/clamd.d/scan.conf
    1. Configure OnAccessIncludePath. Multiple entries can be provided to add additional path locations. The following is a recommended starting point. These can be added in the OnAccessIncludePath section or at the end of the file.
      # The most critical path. Covers Downloads, Desktop, Documents, and email attachments.
      OnAccessIncludePath /home
    2. Configure OnAccessExcludePath. These entries improve performance by avoiding scanning locations regularly used for web browser cache, etc. Replace [USERNAME] with the short username of the device user's account(s). Multiple entries can be provided to add additional path locations. The following is a recommended starting point. Note: Due to a current bug, the wildcard locations may not be properly excluded. It is still recommended to include these exclusions so they are available when the bug is fixed.
      # Prevent scanning cache files
      OnAccessExcludePath /home/*/.cache
      OnAccessExcludePath /home/[USERNAME]/.cache # Prevent scanning trash files OnAccessExcludePath /home/*/.local/share/Trash
      OnAccessExcludePath /home/[USERNAME]/share/Trash

      # Prevent scanning the user's .config folder
      OnAccessExcludePath /home/*/.config
      OnAccessExcludePath /home/[USERNAME]/.config

      # Prevent scanning the user's .local/share folder
      OnAccessExcludePath /home/*/.local/share
      OnAccessExcludePath /home/[USERNAME]/.local/share

      # Prevent scanning browser files
      OnAccessExcludePath /home/*/.mozilla/firefox
      OnAccessExcludePath /home/[USERNAME]/.mozilla/firefox
    3. Uncomment or add the following lines to enable scanning
      OnAccessPrevention yes
      OnAccessExtraScanning yes
    4. Uncomment or add the following lines to prevent the scanning process from triggering scans (looping). Ensure your daemon runs as the listed user (clamscan) by looking for the User line in the config file.
      OnAccessExcludeUname clamscan
  2. Make sure /var/quarantine exists and set permissions
    sudo mkdir /var/quarantine
    sudo chmod 0700 /var/quarantine
  3. Create the systemd Service
    1. Create a new service file
      sudo nano /etc/systemd/system/clamonacc.service
    2. Paste the following into the file
      [Unit]
      description=ClamAV On-Access Scanner
      requires=clamd@scan.service
      after=clamd@scan.service network.target
      
      [Service]
      type=simple
      user=root
      execstart=/usr/sbin/clamonacc -F --config-file=/etc/clamd.d/scan.conf --move=/var/quarantine --fdpass
      restart=on-failure
      restartsec=10s
      
      [Install]
      wantedby=multi-user.target
      
      
  4. Start Real-Time Scanning
    sudo systemctl daemon-reload
    sudo systemctl enable --now clamonacc
  5. Verify clamonacc is running
    sudo systemctl status clamonacc

User Notifications (Optional)

This will attempt to notify the user that downloaded the file. You can customize the script as needed for your environment. The script tries to see who owns the file to determine who might have downloaded it. Ownership will be overwritten by the user folder the file was downloaded to (this allows the script to handle files downloaded using sudo). If ownership can't be determined, it will notify all users on the device.

For notifications, it will provide an alert in all shell sessions (local and ssh) and an alert via the GUI. It also attempts to play an alert sound via the shell and via the GUI.

  1. Install libnotify if not already installed
    sudo dnf install -y libnotify
  2. Create notification script
    sudo nano /usr/local/bin/clamav-notify.sh
  3. Paste the following into the script file
    #!/bin/bash
    
    # Pull the values securely from ClamAV's environment variables
    VIRUS_NAME="$CLAM_VIRUSEVENT_VIRUSNAME"
    FILE_PATH="$CLAM_VIRUSEVENT_FILENAME"
    
    # --- 1. SECURE DEBOUNCE & CACHE ---
    ALERTS_DIR="/run/clamav-alerts"
    mkdir -p "$ALERTS_DIR"
    chmod 700 "$ALERTS_DIR"
    
    # Clean up cache files older than 10 minutes to prevent accumulation
    find "$ALERTS_DIR" -type f -mmin +10 -delete
    
    SAFE_PATH=$(echo "$FILE_PATH" | tr '/' '_')
    CACHE_FILE="${ALERTS_DIR}/clamav_alert_${SAFE_PATH}"
    
    if [ -f "$CACHE_FILE" ]; then
        LAST_ALERT=$(stat -c %Y "$CACHE_FILE" 2>/dev/null)
        now=$(date +%s)
        if [ $((NOW - LAST_ALERT)) -lt 10 ]; then
            exit 0 
        fi
    fi
    touch "$CACHE_FILE"
    
    # --- 2. Log to the system journal ---
    logger -p authpriv.warning -t CLAMAV-ALERT "Malware $VIRUS_NAME found at $FILE_PATH"
    
    # --- 3. Determine who to notify ---
    if [ -f "$FILE_PATH" ]; then
        TARGET_USER=$(stat -c '%U' "$FILE_PATH")
    else
        TARGET_USER="root"
    fi
    
    # OVERRIDE: If the file is in a user's home directory, alert that user directly 
    if [[ "$FILE_PATH" == /home/* ]]; then
        TARGET_USER=$(echo "$FILE_PATH" | awk -F'/' '{print $3}')
    fi
    
    # --- 4. Notification Function ---
    send_alerts() {
        local USER_TO_ALERT="$1"
        
        # Send SSH/Terminal Alert
        if id "$USER_TO_ALERT" &>/dev/null; then
            
            # FILTER: Use 'ps' to find TTYs actively running a shell. 
            # This finds hidden GUI terminals and ignores overlapping SSH/tmux wrappers.
            local USER_TTYS=$(ps -u "$USER_TO_ALERT" -o tty=,comm= | awk '$2 ~ /^(bash|zsh|fish|tcsh|ksh|sh)$/ {print $1}' | grep -E '^(pts/|tty)' | sort -u)
            
            for TERM_DEV in $USER_TTYS; do
                if [ -w "/dev/$TERM_DEV" ]; then
                    echo -e "\a\r\n================================================================\r
    🚨 CLAMAV ALERT: Malware detected! 🚨\r
    Threat: $VIRUS_NAME\r
    Location: $FILE_PATH\r
    Action: Quarantined\r
    ================================================================\r\n" > "/dev/$TERM_DEV"
                fi
            done
        fi
    
        # Send GUI Alert
        local GUI_DISPLAY=$(who | grep "^$USER_TO_ALERT " | grep -E '(:[0-9])' | awk '{print $2}' | head -n 1)
        if [ -n "$GUI_DISPLAY" ]; then
            local UID_TO_ALERT=$(id -u "$USER_TO_ALERT")
            local message=$(printf "<b>Threat:</b> '%s'\n<b>Location:</b> '%s'\n<b>Action:</b> Quarantined" "$VIRUS_NAME" "$FILE_PATH")
    
            # PLAY AUDIO: Automatically detect and use the available audio player
            if command -v pw-play &> /dev/null; then
                sudo -u "$USER_TO_ALERT" env XDG_RUNTIME_DIR="/run/user/$UID_TO_ALERT" pw-play /usr/share/sounds/freedesktop/stereo/dialog-warning.oga 2>/dev/null &
            elif command -v paplay &> /dev/null; then
                sudo -u "$USER_TO_ALERT" env XDG_RUNTIME_DIR="/run/user/$UID_TO_ALERT" paplay /usr/share/sounds/freedesktop/stereo/dialog-warning.oga 2>/dev/null &
            elif command -v canberra-gtk-play &> /dev/null; then
                sudo -u "$USER_TO_ALERT" env display="$GUI_DISPLAY" XDG_RUNTIME_DIR="/run/user/$UID_TO_ALERT" canberra-gtk-play -i dialog-warning 2>/dev/null &
            elif command -v aplay &> /dev/null; then
                sudo -u "$USER_TO_ALERT" env XDG_RUNTIME_DIR="/run/user/$UID_TO_ALERT" aplay /usr/share/sounds/alsa/Front_Center.wav 2>/dev/null &
            fi
    
            # FIRE NOTIFICATION: Send the visual pop-up
            sudo -u "$USER_TO_ALERT" env DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$UID_TO_ALERT/bus" notify-send -u critical -t 10000 -i security-high "ClamAV Threat Detected" "$MESSAGE"
        fi
    }
    
    # --- 5. Execute Alerts ---
    if [ "$TARGET_USER" == "root" ]; then
        for ACTIVE_USER in $(who | awk '{print $1}' | sort -u); do
            send_alerts "$ACTIVE_USER"
        done
    else
        send_alerts "$TARGET_USER"
    fi
  4. Make the script executable
    sudo chmod +x /usr/local/bin/clamav-notify.sh
  5. Grant clamscan sudo permission to execute the script
    sudo visudo -f /etc/sudoers.d/clamav-notify
    1. Add the following two lines to the new file
      Defaults!/usr/local/bin/clamav-notify.sh env_keep += "CLAM_VIRUSEVENT_FILENAME CLAM_VIRUSEVENT_VIRUSNAME"
      clamscan all=(root) NOPASSWD: /usr/local/bin/clamav-notify.sh
  6. Edit the scan config to trigger the notification
    sudo nano /etc/clamd.d/scan.conf
    1. Update or add the following line in the VirusEvent section
      VirusEvent sudo /usr/local/bin/clamav-notify.sh
  7. Restart the Daemons
    sudo systemctl restart clamd@scan clamonacc

Testing

  1. Attempt to download an Eicar test file into a user's home directory. The file should be quarantined to /var/quarantine. You may still see a file downloaded but it shouldn't contain any data.

Ubuntu

This guide is based on a clean Ubuntu 24.04 install using the GlobalProtect 6.2.9-c4 command line client (WiscVPN GlobalProtect (Linux) - Installing, Connecting, and Uninstalling). Most Debian/Ubuntu compatible environments and/or newer client versions should be similar.

Environment & Component Installs

  1. Ensuring updates are applied is recommended
    sudo apt update
    sudo apt upgrade -y
  2. Install ClamAV and AppArmor packages
    sudo apt install -y clamav clamav-daemon apparmor-utils

Configure the Update Service (FreshClam)

  1. Stop the service temporarily so you can manually run the update to verify it is working and you have current definitions. You can make any needed changes to the config file located at /etc/clamav/freshclam.conf
    sudo systemctl stop clamav-freshclam
    sudo freshclam
  2. Enable and start the update service
    sudo systemctl enable --now clamav-freshclam
  3. Verify freshclam is running
    sudo systemctl status clamav-freshclam

Configure the Scanning Daemon (clamd) and Enable Real-Time Protection (clamonacc)

Ubuntu provides a native clamav-clamonacc service. We'll configure the engine and use a systemd drop-in file to apply our specific run flags without breaking future package updates.

  1. Edit the scan config
    sudo nano /etc/clamav/clamd.conf
    1. Uncomment the following line if it is commented
      LocalSocket /var/run/clamav/clamd.ctl
    2. Configure OnAccessIncludePath. Multiple entries can be provided to add additional path locations. The following is a recommended starting point. These can be added in the OnAccessIncludePath section if it exists or at the end of the file.
      # The most critical path. Covers Downloads, Desktop, Documents, and email attachments.
      OnAccessIncludePath /home
    3. Configure OnAccessExcludePath. These entries improve performance by avoiding scanning locations regularly used for web browser cache, etc. Replace [USERNAME] with the short username of the device user's account(s). Multiple entries can be provided to add additional path locations. The following is a recommended starting point. Note: Due to a current bug, the wildcard locations may not be properly excluded. It is still recommended to include these exclusions so they are available when the bug is fixed.
      # Prevent scanning cache files
      OnAccessExcludePath /home/*/.cache OnAccessExcludePath /home/[USERNAME]/.cache # Prevent scanning trash files
      OnAccessExcludePath /home/*/.local/share/Trash OnAccessExcludePath /home/[USERNAME]/.local/share/Trash # Prevent scanning the uesr's .config folder
      OnAccessExcludePath /home/*/.config OnAccessExcludePath /home/[USERNAME]/.config # Prevent scanning containerized browser files
      OnAccessExcludePath home/*/snap/firefox
      OnAccessExcludePath /home/*/snap/firefox
      OnAccessExcludePath home/kwtobin/snap/firefox
      OnAccessExcludePath /home/kwtobin/snap/firefox
    4. Uncomment or add the following lines to enable scanning
      OnAccessPrevention yes
      OnAccessExtraScanning yes
    5. Uncomment or add the following lines to prevent the scanning process from triggering scans (looping). Ensure your daemon runs as the listed user (clamav) by looking for the User line in the config file.
      OnAccessExcludeUname clamav
      OnAccessExcludeRootUID yes
    6. Start clamd
      sudo systemctl enable --now clamav-daemon
    7. Verify clamd is running
      sudo systemctl status clamav-daemon
  2. Configure AppArmor. You have two choices
    • Option 1: Put AppArmor in "complain" mode for clamd. CAUTION: This reduces the security mechanisms AppArmor offers to restrict ClamAV.
      sudo aa-complain /usr/sbin/clamd
    • Option 2: Configure AppArmor to allow ClamAV to scan the home directory (you also may need to add any other locations you configured for scanning). By default, Ubuntu's strict AppArmor profile prevents the background daemon from reading user files and even with this configuration containerized apps can't be scanned properly.
      1. Edit the local AppArmor override file
        sudo nano /etc/apparmor.d/local/usr.sbin.clamd
      2. Add the following lines to allow read access to all home directories
        # Allow the daemon read access to the home directories
        /home/** r,
      3. Prevent AppArmor customizations from being overwritten.
        sudo debconf-set-selections <<< 'clamav-daemon clamav-daemon/apparmor_overwrite boolean false'
      4. Reload the AppArmor profile
        sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.clamd
  3. Configure ACLs to allow clamav to access /home (NOTE the 'X' is captial X, this ensure execute permissions are only applied to directories).
    sudo setfacl -d -m u:clamav:rX /home
    sudo setfacl -R -m u:clamav:rX /home
  4. Create the quarantine directory and set permissions
    sudo mkdir /var/quarantine
    sudo chmod 0700 /var/quarantine
  5. Create the systemd drop-in to modify the native service execution flags
    1. Create the override directory and file
      sudo mkdir -p /etc/systemd/system/clamav-clamonacc.service.d
      sudo nano /etc/systemd/system/clamav-clamonacc.service.d/override.conf
    2. Paste the following into the file to clear the default ExecStart and replace it with our customized flags
      [Unit]
      # Ensure the scanner engine is started first
      Requires=clamav-daemon.service
      After=clamav-daemon.service network.target

      [Service]
      # Clear the default ExecStart and add our custom flags execstart=
      ExecStart=/usr/sbin/clamonacc -F --config-file=/etc/clamav/clamd.conf --move=/var/quarantine --fdpass
      
      
    3. Start Real-Time Scanning
      sudo systemctl daemon-reload
      sudo systemctl enable --now clamav-clamonacc
    4. Verify clamonacc is running
      sudo systemctl status clamav-clamonacc

User Notifications (Optional)

This will attempt to notify the user that downloaded the file. You can customize the script as needed for your environment. The script tries to see who owns the file to determine who might have downloaded it. Ownership will be overwritten by the user folder the file was downloaded to (this allows the script to handle files downloaded using sudo). If ownership can't be determined, it will notify all users on the device.

For notifications, it will provide an alert in all shell sessions (local and ssh) and an alert via the GUI. It also attempts to play an alert sound via the shell and via the GUI.

  1. Install libnotify if not already installed
    sudo apt install -y libnotify-bin
  2. Create notification script
    sudo nano /usr/local/bin/clamav-notify.sh
  3. Paste the following into the script file
    #!/bin/bash
    
    # Pull the values securely from ClamAV's environment variables
    VIRUS_NAME="$CLAM_VIRUSEVENT_VIRUSNAME"
    FILE_PATH="$CLAM_VIRUSEVENT_FILENAME"
    
    # If FILE_PATH starts with /var/quarantine, exit immediately
    if [[ "$FILE_PATH" =~ ^/var/quarantine/ ]]; then
         exit 0
    fi
    
    # --- 1. SECURE DEBOUNCE & CACHE ---
    ALERTS_DIR="/run/clamav-alerts"
    mkdir -p "$ALERTS_DIR"
    chmod 700 "$ALERTS_DIR"
    
    # Clean up cache files older than 10 minutes to prevent accumulation
    find "$ALERTS_DIR" -type f -mmin +10 -delete
    
    SAFE_PATH=$(echo "$FILE_PATH" | tr '/' '_')
    CACHE_FILE="${ALERTS_DIR}/clamav_alert_${SAFE_PATH}"
    
    if [ -f "$CACHE_FILE" ]; then
        LAST_ALERT=$(stat -c %Y "$CACHE_FILE" 2>/dev/null)
        now=$(date +%s)
        if [ $((NOW - LAST_ALERT)) -lt 10 ]; then
            exit 0 
        fi
    fi
    touch "$CACHE_FILE"
    
    # --- 2. Log to the system journal ---
    logger -p authpriv.warning -t CLAMAV-ALERT "Malware $VIRUS_NAME found at $FILE_PATH"
    
    # --- 3. Determine who to notify ---
    if [ -f "$FILE_PATH" ]; then
        TARGET_USER=$(stat -c '%U' "$FILE_PATH")
    else
        TARGET_USER="root"
    fi
    
    # OVERRIDE: If the file is in a user's home directory, alert that user directly 
    if [[ "$FILE_PATH" == /home/* ]]; then
        TARGET_USER=$(echo "$FILE_PATH" | awk -F'/' '{print $3}')
    fi
    
    # --- 4. Notification Function ---
    send_alerts() {
        local USER_TO_ALERT="$1"
        
        # Send SSH/Terminal Alert
        if id "$USER_TO_ALERT" &>/dev/null; then
            
            # FILTER: Use 'ps' to find TTYs actively running a shell. 
            # This finds hidden GUI terminals and ignores overlapping SSH/tmux wrappers.
            local USER_TTYS=$(ps -u "$USER_TO_ALERT" -o tty=,comm= | awk '$2 ~ /^(bash|zsh|fish|tcsh|ksh|sh)$/ {print $1}' | grep -E '^(pts/|tty)' | sort -u)
            
            for TERM_DEV in $USER_TTYS; do
                if [ -w "/dev/$TERM_DEV" ]; then
                    echo -e "\a\r\n================================================================\r
    🚨 CLAMAV ALERT: Malware detected! 🚨\r
    Threat: $VIRUS_NAME\r
    Location: $FILE_PATH\r
    Action: Quarantined\r
    ================================================================\r\n" > "/dev/$TERM_DEV"
                fi
            done
        fi
    
        # Send GUI Alert (Wayland & X11 compatible)
        local UID_TO_ALERT=$(id -u "$USER_TO_ALERT")
        local DBUS_PATH="/run/user/$UID_TO_ALERT/bus"
        
        if [ -S "$DBUS_PATH" ]; then
            local message=$(printf "Threat: '%s'\nLocation: '%s'\nAction: Quarantined" "$VIRUS_NAME" "$FILE_PATH")
    
            # PLAY AUDIO: Automatically detect and use the available audio player
            if command -v pw-play &> /dev/null; then
                sudo -u "$USER_TO_ALERT" env XDG_RUNTIME_DIR="/run/user/$UID_TO_ALERT" pw-play /usr/share/sounds/freedesktop/stereo/dialog-warning.oga 2>/dev/null &
            elif command -v paplay &> /dev/null; then
                sudo -u "$USER_TO_ALERT" env XDG_RUNTIME_DIR="/run/user/$UID_TO_ALERT" paplay /usr/share/sounds/freedesktop/stereo/dialog-warning.oga 2>/dev/null &
            elif command -v aplay &> /dev/null; then
                sudo -u "$USER_TO_ALERT" env XDG_RUNTIME_DIR="/run/user/$UID_TO_ALERT" aplay /usr/share/sounds/alsa/Front_Center.wav 2>/dev/null &
            fi
    
            # FIRE NOTIFICATION: Send the visual pop-up
            sudo -u "$USER_TO_ALERT" env DBUS_SESSION_BUS_ADDRESS="unix:path=$DBUS_PATH" notify-send -u critical -t 10000 -i security-high "ClamAV Threat Detected" "$MESSAGE"
        fi
    }
    
    # --- 5. Execute Alerts ---
    if [ "$TARGET_USER" == "root" ]; then
        for ACTIVE_USER in $(who | awk '{print $1}' | sort -u); do
            send_alerts "$ACTIVE_USER"
        done
    else
        send_alerts "$TARGET_USER"
    fi
  4. Make the script executable
    sudo chmod +x /usr/local/bin/clamav-notify.sh
  5. Grant clamav sudo permission to execute the script
    sudo visudo -f /etc/sudoers.d/clamav-notify
    1. Add the following two lines to the new file
      Defaults!/usr/local/bin/clamav-notify.sh env_keep += "CLAM_VIRUSEVENT_FILENAME CLAM_VIRUSEVENT_VIRUSNAME"
      clamav all=(root) NOPASSWD: /usr/local/bin/clamav-notify.sh
  6. If the aa-complain AppArmor option wasn't used, configure AppArmor to allow clamav to access the notification script.
    1. Edit the local AppArmor override file
      sudo nano /etc/apparmor.d/local/usr.sbin.clamd
    2. Add the following lines to trigger the notification script
      # Allow clamd to spawn the shell required to run the VirusEvent command
      /usr/bin/dash ix,
      /usr/bin/sh ix,

      # Allow the daemon to execute the notification script via sudo
      /usr/bin/sudo PUx,
      /usr/local/bin/clamav-notify.sh PUx,
    3. Reload the AppArmor profile
      sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.clamd
  7. Edit the scan config to trigger the notification
    sudo nano /etc/clamav/clamd.conf
    1. Update or add the following line in the VirusEvent section
      # Trigger notification script on event
      VirusEvent sudo /usr/local/bin/clamav-notify.sh
  8. Restart the Daemons
    sudo systemctl restart clamav-daemon clamav-clamonacc

Testing

  1. Attempt to download an Eicar test file into a user's home directory. The file should be quarantined to /var/quarantine. You may still see a file downloaded but it shouldn't contain any data.


Keywords:
antivirus AV realtime real time HIP host information profile check checks clam config redhat ubuntu linux on-access 
Doc ID:
160333
Owned by:
Kerry T. in Smart Access
Created:
2026-03-31
Updated:
2026-06-01
Sites:
Smart Access