ClamAV Real-Time Protection Configuration (Linux)
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
- Ensuring updates are applied is recommended
sudo dnf update -y
- Install EPEL repository if not already installed
sudo dnf install -y epel-release
- Install ClamAV packages
sudo dnf install -y clamav clamd clamav-update clamav-server clamav-scanner-systemd
Configure the Update Service (FreshClam)
- 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.confsudo freshclam
- Enable the update service
sudo systemctl enable --now clamav-freshclam
- Verify
freshclamis runningsudo systemctl status clamav-freshclam
Configure the Scanning Daemon (clamd)
- Edit the scan config
sudo nano /etc/clamd.d/scan.conf
- Uncomment the following line
LocalSocket /run/clamd.scan/clamd.sock
- Uncomment the following line
- Start
clamdsudo systemctl enable --now clamd@scan
- Verify
clamdis runningsudo 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.
- Edit the scan config
sudo nano /etc/clamd.d/scan.conf
- Configure
OnAccessIncludePath. Multiple entries can be provided to add additional path locations. The following is a recommended starting point. These can be added in theOnAccessIncludePathsection or at the end of the file.# The most critical path. Covers Downloads, Desktop, Documents, and email attachments. OnAccessIncludePath /home
- 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 - Uncomment or add the following lines to enable scanning
OnAccessPrevention yes OnAccessExtraScanning yes
- 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
- Configure
- Make sure
/var/quarantineexists and set permissionssudo mkdir /var/quarantine sudo chmod 0700 /var/quarantine
- Create the
systemdService- Create a new service file
sudo nano /etc/systemd/system/clamonacc.service
- 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
- Create a new service file
- Start Real-Time Scanning
sudo systemctl daemon-reload sudo systemctl enable --now clamonacc
- Verify
clamonaccis runningsudo 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.
- Install
libnotifyif not already installedsudo dnf install -y libnotify
- Create notification script
sudo nano /usr/local/bin/clamav-notify.sh
- 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 - Make the script executable
sudo chmod +x /usr/local/bin/clamav-notify.sh
- Grant
clamscansudo permission to execute the scriptsudo visudo -f /etc/sudoers.d/clamav-notify
- 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
- Add the following two lines to the new file
- Edit the scan config to trigger the notification
sudo nano /etc/clamd.d/scan.conf
- Update or add the following line in the VirusEvent section
VirusEvent sudo /usr/local/bin/clamav-notify.sh
- Update or add the following line in the VirusEvent section
- Restart the Daemons
sudo systemctl restart clamd@scan clamonacc
Testing
- 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
- Ensuring updates are applied is recommended
sudo apt update
sudo apt upgrade -y - Install ClamAV and AppArmor packages
sudo apt install -y clamav clamav-daemon apparmor-utils
Configure the Update Service (FreshClam)
- 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 - Enable and start the update service
sudo systemctl enable --now clamav-freshclam
- Verify
freshclamis runningsudo 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.
- Edit the scan config
sudo nano /etc/clamav/clamd.conf
- Uncomment the following line if it is commented
LocalSocket /var/run/clamav/clamd.ctl
- Configure
OnAccessIncludePath. Multiple entries can be provided to add additional path locations. The following is a recommended starting point. These can be added in theOnAccessIncludePathsection if it exists or at the end of the file.# The most critical path. Covers Downloads, Desktop, Documents, and email attachments. OnAccessIncludePath /home
- 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 - Uncomment or add the following lines to enable scanning
OnAccessPrevention yes OnAccessExtraScanning yes
- 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 - Start
clamdsudo systemctl enable --now clamav-daemon
- Verify
clamdis runningsudo systemctl status clamav-daemon
- Uncomment the following line if it is commented
- 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.
- Edit the local AppArmor override file
sudo nano /etc/apparmor.d/local/usr.sbin.clamd
- Add the following lines to allow read access to all home directories
# Allow the daemon read access to the home directories
/home/** r, - Prevent AppArmor customizations from being overwritten.
sudo debconf-set-selections <<< 'clamav-daemon clamav-daemon/apparmor_overwrite boolean false'
- Reload the AppArmor profile
sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.clamd
- Edit the local AppArmor override file
- Option 1: Put AppArmor in "complain" mode for clamd. CAUTION: This reduces the security mechanisms AppArmor offers to restrict ClamAV.
- 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 - Create the quarantine directory and set permissions
sudo mkdir /var/quarantine sudo chmod 0700 /var/quarantine
- Create the
systemddrop-in to modify the native service execution flags- 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 - 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 - Start Real-Time Scanning
sudo systemctl daemon-reload sudo systemctl enable --now clamav-clamonacc
- Verify
clamonaccis runningsudo systemctl status clamav-clamonacc
- Create the override directory and file
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.
- Install
libnotifyif not already installedsudo apt install -y libnotify-bin
- Create notification script
sudo nano /usr/local/bin/clamav-notify.sh
- 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 - Make the script executable
sudo chmod +x /usr/local/bin/clamav-notify.sh
- Grant
clamavsudo permission to execute the scriptsudo visudo -f /etc/sudoers.d/clamav-notify
- 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
- Add the following two lines to the new file
- If the aa-complain AppArmor option wasn't used, configure AppArmor to allow
clamavto access the notification script.- Edit the local AppArmor override file
sudo nano /etc/apparmor.d/local/usr.sbin.clamd
- 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, - Reload the AppArmor profile
sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.clamd
- Edit the local AppArmor override file
- Edit the scan config to trigger the notification
sudo nano /etc/clamav/clamd.conf
- Update or add the following line in the VirusEvent section
# Trigger notification script on event
VirusEvent sudo /usr/local/bin/clamav-notify.sh
- Update or add the following line in the VirusEvent section
- Restart the Daemons
sudo systemctl restart clamav-daemon clamav-clamonacc
Testing
- 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.