What's here:

Installing the Latest Minecraft Java Server on Linux Mint and Connecting from Windows 11

Running a private Minecraft server is a useful home-lab project because it combines Linux administration, Java runtime management, firewall configuration, systemd service management, and basic client/server troubleshooting. This guide explains how to install the latest Java-based Minecraft server on the latest Linux Mint release, then connect to it from a Windows 11 Minecraft client.

As of May 23, 2026, the latest Linux Mint release listed by the project is Linux Mint 22.3 'Zena,' and the latest Minecraft Java release identified on Minecraft.net is Minecraft Java Edition 26.1.2, published April 9, 2026. Future readers should always confirm the current version on the official Linux Mint and Minecraft download pages before installing.

Java Edition versus Bedrock Edition

This article is about the Minecraft: Java Edition server, distributed as a .jar file. Mojang's server download page states that the Java Edition server setup is compatible with Minecraft: Java Edition, not Bedrock Edition. That distinction matters because a normal Windows Bedrock client will not connect directly to a vanilla Java Edition server.

For this setup, the Windows 11 client must run:

Minecraft: Java Edition

not:

Minecraft for Windows

The Minecraft Launcher can provide access to both editions, but the edition selected in the launcher must match the server type. The Microsoft Store listing for the launcher identifies it as available for Windows 10 and Windows 11 and notes that it provides access to Minecraft: Java Edition and Minecraft for Windows.

Install Java 25 on Linux Mint

Recent Minecraft Java versions require a newer Java runtime. The Minecraft Java Edition 26.1 release notes state that the game now requires Java 25 and that the included Java distribution is the Microsoft build of OpenJDK 25.

Linux Mint may not include Java 25 in its default repositories, so a practical approach is to install Eclipse Temurin from Adoptium. Adoptium provides DEB packages for Debian and Ubuntu-style systems, and its documentation specifically notes that Linux Mint users should use UBUNTU_CODENAME instead of VERSION_CODENAME when configuring the repository.

Install Java 25:

sudo apt update
sudo apt install -y wget apt-transport-https gpg

wget -qO - https://packages.adoptium.net/artifactory/api/gpg/key/public \
| gpg --dearmor \
| sudo tee /etc/apt/trusted.gpg.d/adoptium.gpg > /dev/null

echo "deb https://packages.adoptium.net/artifactory/deb $(awk -F= '/^UBUNTU_CODENAME/{print$2}' /etc/os-release) main" \
| sudo tee /etc/apt/sources.list.d/adoptium.list

sudo apt update
sudo apt install -y temurin-25-jdk

 Verify the Java version:

java -version

 You should see output showing Java 25. If multiple Java versions are installed, you can use the full Java path later in the systemd service to avoid accidentally launching Minecraft with the wrong runtime.

Create a Minecraft server user and directory

Do not run the Minecraft server as root. Create a dedicated service account and server directory:

sudo useradd --system --home-dir /opt/minecraft --shell /usr/sbin/nologin minecraft
sudo mkdir -p /opt/minecraft
sudo chown -R minecraft:minecraft /opt/minecraft

Switch into the server directory:

cd /opt/minecraft

 Download the Minecraft server .jar

Download the official Java Edition server .jar from Mojang's Minecraft server download page or from the latest release article. Mojang's server download page states that downloading the software means agreeing to the Minecraft End User License Agreement and Privacy Policy, so this step should be done from the official Minecraft source.

Use a browser to open the official Minecraft server download page, right-click the server .jar link, and copy the link address. Then download it on the Linux Mint server:

sudo -u minecraft wget -O /opt/minecraft/server.jar '<paste official Minecraft server.jar URL here>'

For future-proof documentation, I recommend naming the downloaded file server.jar and documenting the actual Minecraft version separately in a text file:

echo "Minecraft Java Server installed from official Mojang server.jar link" | sudo tee /opt/minecraft/README.txt
sudo chown minecraft:minecraft /opt/minecraft/README.txt

 First start and EULA acceptance

Start the server once to generate the initial configuration files:

sudo -u minecraft /usr/lib/jvm/temurin-25-jdk-amd64/bin/java -Xms2G -Xmx4G -jar /opt/minecraft/server.jar nogui

 The server will stop and create an eula.txt file. Edit it:

sudo vi /opt/minecraft/eula.txt

 Change:

eula=false

 to:

eula=true

 Save the file. Mojang's server download page explains that the server is run from the command line with Java and that the nogui option can be omitted only if the graphical interface is desired. On a Linux server, nogui is normally the right choice.

Start the server again:

sudo -u minecraft /usr/lib/jvm/temurin-25-jdk-amd64/bin/java -Xms2G -Xmx4G -jar /opt/minecraft/server.jar nogui

 When the server finishes loading, you should see a message indicating that it is ready. Stop it with:

Ctrl + C

Configure basic server settings

The main configuration file is:

/opt/minecraft/server.properties

 Edit it:

sudo vi /opt/minecraft/server.properties

 For a simple home server, these are the most important settings to review:

server-port=25565
server-ip=
motd=A Linux Mint Minecraft Server
enable-command-block=false
white-list=false
online-mode=true
difficulty=easy
gamemode=survival

 Leave server-ip= blank unless you have a specific reason to bind the server to only one address. For most home and lab servers, leaving it blank allows Minecraft to listen normally on the system's available interfaces.

Open the firewall

Minecraft Java Edition normally listens on TCP port 25565. If UFW is enabled on Linux Mint, allow that port:

sudo ufw allow 25565/tcp
sudo ufw status verbose

 Find the Linux server's IP address:

ip -o -4 addr show up scope global

 You should see an address such as:

192.168.1.50/24

 The Windows client will use that IP address to connect.

Manage the server with systemd

A manual command is useful for testing, but a real server should be managed by systemd. Create a service file:

sudo vi /etc/systemd/system/minecraft.service

 Add:

[Unit]
Description=Minecraft Java Server
After=network.target

[Service]
Type=simple
User=minecraft
Group=minecraft
WorkingDirectory=/opt/minecraft
ExecStart=/usr/lib/jvm/temurin-25-jdk-amd64/bin/java -Xms2G -Xmx4G -jar /opt/minecraft/server.jar nogui
Restart=on-failure
RestartSec=10
SuccessExitStatus=0 143

[Install]
WantedBy=multi-user.target

 Reload systemd and start the service:

sudo systemctl daemon-reload
sudo systemctl enable --now minecraft

 Check status:

systemctl status minecraft

 Watch the live logs:

journalctl -u minecraft -f

 Useful management commands:

sudo systemctl start minecraft
sudo systemctl stop minecraft
sudo systemctl restart minecraft
systemctl status minecraft
journalctl -u minecraft -n 100

Troubleshooting Java version errors

If the server fails with an error such as:

UnsupportedClassVersionError

the Minecraft server .jar was compiled for a newer Java runtime than the one used to start it. Since Minecraft 26.1 requires Java 25, the fix is to make sure the systemd service uses Java 25 directly:

/usr/lib/jvm/temurin-25-jdk-amd64/bin/java -version

If that shows Java 25, use that exact path in the ExecStart= line of the systemd service.

Install the Minecraft client on Windows 11

On the Windows 11 PC, install the Minecraft Launcher from the Microsoft Store or from the official Minecraft download page. Minecraft's download page says the launcher can be installed through the Microsoft Store or from Minecraft.net, and the official Help Center provides instructions for downloading and installing the launcher.

On Windows 11:

1.       Open Microsoft Store.

2.       Search for Minecraft Launcher.

3.       Install Minecraft Launcher.

4.       Open the launcher.

5.       Sign in with the Microsoft account that owns Minecraft.

6.       Select Minecraft: Java Edition.

7.       Use Latest Release, or choose the exact version that matches the server.

8.       Click Play.

The client and server should run the same Minecraft Java version. Mojang's 26.1.2 release notes instruct players to use the Minecraft Launcher and set it to 'Latest Release' to install the release.

If you need to select a specific version:

Minecraft Launcher → Minecraft: Java Edition → Installations → New Installation

Then choose the version that matches the server.

Connect from Windows 11 to the Linux Mint server

Once Minecraft Java Edition is running on Windows 11:

9.       Click Multiplayer.

10.   Click Add Server or Direct Connection.

11.   Enter the Linux Mint server IP address.

12.   Click Join Server.

If the server IP is:

192.168.1.50

and the server is using the default port, enter:

192.168.1.50

If you changed the port, enter it like this:

192.168.1.50:25565

If the client cannot connect, test from Windows PowerShell:

Test-NetConnection 192.168.1.50 -Port 25565

A successful result should show:

TcpTestSucceeded : True

If that test fails, check these items:

·         The Minecraft service is running on Linux.

·         The Linux firewall allows TCP 25565.

·         The Windows client is on the same network or has a route to the server.

·         The client is running Minecraft: Java Edition, not Bedrock Edition.

·         The client version matches the server version.

·         The server.properties file uses the correct port.

Optional: allow only known players

For a private server, consider enabling the whitelist:

sudo vi /opt/minecraft/server.properties

Set:

white-list=true

Restart the server:

sudo systemctl restart minecraft

Add a player from the Minecraft server console or by using the server command interface:

whitelist add PlayerName

You can send commands to the running server through the service logs only if you have set up a console method such as screen, tmux, or a management wrapper. For a basic setup, stopping the service and running the server manually for administration is sometimes simpler.

Final checklist

A working Linux Mint Minecraft Java server should have:

·         Linux Mint installed and updated.

·         Java 25 installed.

·         Official Minecraft Java server.jar downloaded from Mojang.

·         Dedicated minecraft service account.

·         EULA accepted.

·         server.properties reviewed.

·         TCP port 25565 allowed through the firewall.

·         minecraft.service enabled and running.

·         Windows 11 client running Minecraft: Java Edition.

·         Client version matching the server version.

This setup gives you a stable vanilla Minecraft Java server that starts automatically, runs as a non-root user, logs through systemd, and is accessible from Windows 11 clients on the network.

References

Adoptium. (n.d.). Linux (RPM/DEB/APK) installer packages. Eclipse Foundation. https://adoptium.net/installation/linux

Linux Mint. (n.d.). Download Linux Mint 22.3. https://linuxmint.com/download.php

Microsoft. (n.d.). Minecraft Launcher. Xbox. https://www.xbox.com/en-US/games/store/minecraft-launcher/9pgw18npbzv5

Mojang. (2026, March 24). Minecraft Java Edition 26.1. Minecraft. https://www.minecraft.net/en-us/article/minecraft-java-edition-26-1

Mojang. (2026, April 9). Minecraft Java Edition 26.1.2. Minecraft. https://www.minecraft.net/en-us/article/minecraft-java-edition-26-1-2

Mojang. (n.d.-a). Download Minecraft & server software. Minecraft. https://www.minecraft.net/en-us/download

Mojang. (n.d.-b). Download the Minecraft: Java Edition server. Minecraft. https://www.minecraft.net/en-us/download/server

Mojang. (n.d.-c). How to download and install the Minecraft Launcher. Minecraft Help Center. https://help.minecraft.net/hc/en-us/articles/23907917790093-How-to-Download-and-Install-the-

How to Run a Script on a Schedule with systemd Timers

Most Linux administrators know about cron, but modern Linux systems also include another powerful scheduling option: systemd timers. A systemd timer lets you run a script on a schedule using the same management framework you already use for services. That means you can start, stop, enable, disable, inspect, log, and troubleshoot scheduled jobs with familiar systemctl and journalctl commands.

The Basic Pattern

A scheduled systemd job normally has three parts: a script that does the work, a .service unit that runs the script, and a .timer unit that schedules the service.

1. A script that does the work.
2. A .service unit that runs the script.
3. A .timer unit that schedules the service.

Step 1: Create a Script

sudo vi /usr/local/sbin/example-job.sh

Add the following script:

#!/bin/bash

LOGFILE="/var/log/example-job.log"

{
    echo "============================================================"
    echo "Example job started: $(date)"
    echo "Hostname: $(hostname)"
    echo "This is where the scheduled work would happen."
    echo "Example job finished: $(date)"
    echo "============================================================"
    echo
} >> "$LOGFILE"

exit 0

Make the script executable:

sudo chmod 750 /usr/local/sbin/example-job.sh
sudo chown root:root /usr/local/sbin/example-job.sh

Test the script manually before involving systemd:

sudo /usr/local/sbin/example-job.sh
sudo tail -n 20 /var/log/example-job.log

Step 2: Create the systemd Service

sudo vi /etc/systemd/system/example-job.service

Create the following service unit:

[Unit]
Description=Run the example scheduled job

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/example-job.sh

Reload systemd and test the service:

sudo systemctl daemon-reload
sudo systemctl start example-job.service
systemctl status example-job.service
journalctl -u example-job.service -n 50

Step 3: Create the systemd Timer

sudo vi /etc/systemd/system/example-job.timer

Create the timer unit:

[Unit]
Description=Run the example scheduled job once per day

[Timer]
OnCalendar=*-*-* 07:00:00
Persistent=true
Unit=example-job.service

[Install]
WantedBy=timers.target

This timer runs the script once per day at 7:00 AM local server time. Persistent=true means the job will run later if the system was powered off during the scheduled time.

Step 4: Enable and Start the Timer

sudo systemctl daemon-reload
sudo systemctl enable --now example-job.timer

Check the timer and view the next scheduled run:

systemctl status example-job.timer
systemctl list-timers --all | grep example-job

Step 5: Test and Troubleshoot

To manually run the scheduled job immediately:

sudo systemctl start example-job.service

To view logs from the service:

journalctl -u example-job.service -n 100
journalctl -u example-job.service -f

To stop the timer temporarily:

sudo systemctl stop example-job.timer

To disable the timer permanently:

sudo systemctl disable --now example-job.timer

Common OnCalendar Examples

Schedule

OnCalendar Value

Every day at 7:00 AM

*-*-* 07:00:00

Every hour

hourly

Every Monday at 1:00 AM

Mon *-*-* 01:00:00

Every day at midnight

daily

Every 15 minutes

*:0/15

Final Thoughts

systemd timers provide a clean and modern way to schedule recurring jobs on Linux. Unlike cron, timers integrate directly with systemctl and journalctl, making them easier to manage, troubleshoot, and monitor. Once you understand the pattern of script, service, and timer, you can apply it to backups, antivirus scans, maintenance tasks, monitoring scripts, and many other server administration jobs.

References

freedesktop.org. systemd.timer: Timer unit configuration. https://www.freedesktop.org/software/systemd/man/systemd.timer.html 

freedesktop.org. systemd.service: Service unit configuration. https://www.freedesktop.org/software/systemd/man/systemd.service.html

freedesktop.org. systemctl: Control the systemd system and service manager. https://www.freedesktop.org/software/systemd/man/systemctl.html

Configuring a Home File Server on Linux Mint

Introduction

A home Linux file server often becomes the quiet workhorse of the household network. It stores documents, photos, installers, backups, archives, media, exported projects, and random files copied from Windows machines. When that same directory is shared to Windows 11 clients through Samba and published over HTTP by nginx, it becomes even more useful. It also becomes a place where malware, unwanted scripts, malicious documents, or infected downloads can sit unnoticed.

That is where ClamAV fits. ClamAV is not a replacement for good backups, careful permissions, patching, endpoint protection on Windows clients, or common sense. However, for a Linux Mint 21.3 home file server sharing /data/ through Samba and nginx, ClamAV provides a practical open-source antivirus layer. The official ClamAV documentation describes it as an open-source toolkit for detecting viruses, Trojans, malware, and other threats, with tools such as freshclam, clamd, clamscan, and clamdscan (Cisco Talos, n.d.-a).

Linux Mint 21.3 uses an Ubuntu Jammy package base, which makes the installation process familiar for Ubuntu-style administration (Linux Mint, n.d.).

The Target Design

The design in this article assumes a simple home server layout:

Windows 11 clients -> Samba share -> /data/

nginx web server   -> HTTP access -> /data/

ClamAV daemon      -> scheduled malware scans of /data/

freshclam          -> automatic signature updates

rsync              -> scheduled backup from /data/ to /data-backup/

systemd timers     -> automation for scans and backups

The goal is to protect the shared file repository without making the server difficult to maintain. Files can arrive through Samba, be served later by nginx, and be scanned regularly by ClamAV. A separate scheduled rsync job keeps a local backup copy in /data-backup/.

Install ClamAV

Install the ClamAV packages:

sudo apt update

sudo apt install clamav clamav-daemon

The clamav package provides the basic scanner tools. The clamav-daemon package provides clamd, the scanning daemon. ClamAV documentation explains that clamdscan sends scan requests to clamd, while daemon behavior is controlled largely through clamd.conf (Cisco Talos, n.d.-b).

Enable and start the services:

sudo systemctl enable --now clamav-freshclam

sudo systemctl enable --now clamav-daemon

Check status:

systemctl status clamav-freshclam

systemctl status clamav-daemon

The clamav-freshclam service updates virus signature databases. The clamav-daemon service runs the scanner daemon.

Confirm Signature Updates

A healthy freshclam log should show database updates such as:

daily.cvd updated

main.cvd updated

bytecode.cvd updated

Database test passed

Check the updater logs:

sudo journalctl -u clamav-freshclam -n 100

sudo tail -n 100 /var/log/clamav/freshclam.log

A warning such as the following usually means the updater could not notify the scanning daemon:

WARNING: Clamd was NOT notified: Cannot connect to clamd through /var/run/clamav/clamd.ctl

That normally means clamd is not installed, is not running, or its socket was not available when freshclam tried to notify it. Restart both services:

sudo systemctl restart clamav-daemon

sudo systemctl restart clamav-freshclam

Then verify:

systemctl status clamav-daemon

systemctl status clamav-freshclam

Check ClamAV Logging

ClamAV logs are usually found under:

/var/log/clamav/

Useful log commands:

ls -lh /var/log/clamav/

sudo tail -f /var/log/clamav/clamav.log

sudo tail -f /var/log/clamav/freshclam.log

sudo journalctl -u clamav-daemon -f

sudo journalctl -u clamav-freshclam -f

To confirm the configured log file:

grep -E '^(LogFile|LogSyslog|LogFacility|LogFileMaxSize)' /etc/clamav/clamd.conf

The clamd.conf file controls daemon behavior, including logging, socket settings, scan limits, recursion limits, and thread configuration (Canonical, n.d.-a).

Make systemctl Easier to Use

On many systems, systemctl status opens in a pager. To make systemd tools behave as though --no-pager was used, set SYSTEMD_PAGER=cat:

echo 'export SYSTEMD_PAGER=cat' >> ~/.bashrc

source ~/.bashrc

For root:

echo 'export SYSTEMD_PAGER=cat' >> /root/.bashrc

source /root/.bashrc

Test clamdscan

Start small before scanning the full share:

sudo clamdscan -v --fdpass /etc/hosts

Then test the shared directory:

sudo clamdscan -v --fdpass --multiscan /data/

Useful clamdscan options include the following:

Option

Use

-v, --verbose

Shows more output during a scan.

--fdpass

Passes open file descriptors to clamd. This is useful when clamd runs as a different user.

--multiscan

Uses multiple daemon worker threads for directory scans.

--log=FILE

Writes scan output to a specified log file.

--move=DIRECTORY

Moves infected files to a quarantine directory.

--copy=DIRECTORY

Copies infected files to a quarantine directory while leaving the original in place.

--remove

Deletes infected files. Use cautiously on shared storage.

--reload

Asks clamd to reload signatures.

--ping

Checks whether clamd responds.

 

The --fdpass option is especially useful on a local Linux server because clamdscan can open files and pass the file descriptors to the daemon. ClamAV documentation notes that --multiscan lets directory scans use multiple daemon worker threads, and when combined with --fdpass, clamdscan walks the directory tree and submits individual file scans to the daemon (Cisco Talos, n.d.-b).

A practical manual scan command is:

sudo clamdscan -v --fdpass --multiscan --log=/var/log/clamav/manual-data-scan.log /data/

Prepare Permissions for Samba, nginx, ClamAV, and Backups

A file server serving the same directory through Samba and nginx has multiple access paths. Each service needs only the access required for its role.

Component

Access requirement

Samba users or groups

Write and read files through the Windows file share.

nginx worker process

Read files when serving static content over HTTP.

ClamAV scanner

Read files during scheduled malware scans.

Backup process

Read the source directory and write to the backup destination.

 

A practical pattern is to use a shared Linux group for write access and ACLs for service read access.

Create a file-sharing group:

sudo groupadd fileshare

Set group ownership:

sudo chgrp -R fileshare /data/

sudo chmod -R 2775 /data/

The 2 in 2775 sets the setgid bit on directories, helping new files inherit the directory group.

Grant nginx read and directory traversal access:

sudo setfacl -R -m u:www-data:rx /data/

sudo setfacl -R -d -m u:www-data:rx /data/

Grant ClamAV read and directory traversal access:

sudo setfacl -R -m u:clamav:rx /data/

sudo setfacl -R -d -m u:clamav:rx /data/

Verify ACLs:

getfacl /data/

When scans are launched with sudo clamdscan --fdpass, the root-launched client can open files and pass them to clamd. Even so, explicit ACLs for the clamav user make future troubleshooting easier.

Example Samba Share

A simple Samba share for /data/ might look like this:

sudo nano /etc/samba/smb.conf

Example share definition:

[data]

    path = /data

    browseable = yes

    read only = no

    guest ok = no

    valid users = @fileshare

    force group = fileshare

    create mask = 0664

    directory mask = 2775

Restart Samba:

sudo systemctl restart smbd nmbd

Windows 11 clients can connect using:

\\server-name\data

\\server-ip-address\data

Samba also provides a vfs_virusfilter module for on-access antivirus scanning on Samba file services, but scheduled scans are simpler and easier to troubleshoot for a home server. The Samba documentation describes vfs_virusfilter as a stackable VFS module for scanning and filtering virus files on Samba file services (Samba Team, n.d.).

Example nginx Static File Share

nginx can publish /data/ over HTTP as static content. NGINX documentation explains that the root directive defines the filesystem path used to serve requested files (NGINX, n.d.).

Create a site file:

sudo nano /etc/nginx/sites-available/data-share

Example configuration:

server {

    listen 80;

    server_name server-name;

 

    root /data;

 

    location / {

        autoindex on;

        try_files $uri $uri/ =404;

    }

}

Enable the site:

sudo ln -s /etc/nginx/sites-available/data-share /etc/nginx/sites-enabled/data-share

sudo nginx -t

sudo systemctl reload nginx

For a home LAN-only service, restrict access by subnet:

server {

    listen 80;

    server_name server-name;

 

    root /data;

 

    location / {

        allow 192.168.1.0/24;

        deny all;

        autoindex on;

        try_files $uri $uri/ =404;

    }

}

Adjust the subnet to match the local network.

Schedule ClamAV Scans with systemd

A systemd timer is a clean way to schedule scans. Timers are unit files ending in .timer and can activate matching .service units on a calendar schedule. The systemd.timer documentation describes timer units and the use of calendar-based scheduling through timer configuration (freedesktop.org, n.d.).

Create the scan script:

sudo nano /usr/local/sbin/clamav-scan-data.sh

Paste:

#!/bin/bash

 

LOGFILE="/var/log/clamav/scheduled-data-scan.log"

LOCKFILE="/run/lock/clamav-scan-data.lock"

SCAN_TARGET="/data/"

 

{

    echo "============================================================"

    echo "ClamAV scheduled scan started: $(date)"

    echo "Target: ${SCAN_TARGET}"

    echo "============================================================"

} >> "$LOGFILE"

 

/usr/bin/flock -n "$LOCKFILE"     /usr/bin/nice -n 10 /usr/bin/ionice -c2 -n7     /usr/bin/clamdscan -v --fdpass --multiscan --log="$LOGFILE" "$SCAN_TARGET"

 

EXIT_CODE=$?

 

{

    echo "============================================================"

    echo "ClamAV scheduled scan finished: $(date)"

    echo "Exit code: ${EXIT_CODE}"

    echo "Exit code meaning: 0=no virus found, 1=virus found, 2=error"

    echo "============================================================"

    echo

} >> "$LOGFILE"

 

exit "$EXIT_CODE"

Make it executable:

sudo chmod 750 /usr/local/sbin/clamav-scan-data.sh

Test it:

sudo /usr/local/sbin/clamav-scan-data.sh

sudo tail -n 50 /var/log/clamav/scheduled-data-scan.log

Create the service:

sudo nano /etc/systemd/system/clamav-data-scan.service

Paste:

[Unit]

Description=Scheduled ClamAV scan of /data

Wants=clamav-daemon.service

After=clamav-daemon.service

 

[Service]

Type=oneshot

ExecStart=/usr/local/sbin/clamav-scan-data.sh

Create the timer:

sudo nano /etc/systemd/system/clamav-data-scan.timer

Paste:

[Unit]

Description=Run scheduled ClamAV scan of /data

 

[Timer]

OnCalendar=*-*-* 02:00:00

Persistent=true

Unit=clamav-data-scan.service

 

[Install]

WantedBy=timers.target

Enable the timer:

sudo systemctl daemon-reload

sudo systemctl enable --now clamav-data-scan.timer

Check it:

systemctl list-timers --all | grep clamav

systemctl status clamav-data-scan.timer

systemctl status clamav-data-scan.service

Watch the scan log:

sudo tail -f /var/log/clamav/scheduled-data-scan.log

Persistent=true is useful on a home server because the timer can catch up after a missed run if the machine was powered off during the scheduled time (freedesktop.org, n.d.).

Add a Scheduled rsync Backup from /data/ to /data-backup/

Antivirus is not a backup strategy. A clean ClamAV scan only means malware was not detected by the available signatures and scan engine. A backup helps protect against accidental deletion, hardware failure, user mistakes, ransomware damage, failed upgrades, and administrative errors.

This example uses rsync to mirror the contents of /data/ into /data-backup/.

Create the destination directory:

sudo mkdir -p /data-backup

Run a first manual test:

sudo rsync -aHAX --delete --numeric-ids /data/ /data-backup/

The trailing slash on /data/ matters. It means copy the contents of /data/ into /data-backup/, rather than copying the /data directory itself as a subdirectory. The rsync manual documents archive mode, deletion options, and local-copy behavior (rsync project, n.d.).

The options used here are:

-a              Archive mode

-H              Preserve hard links

-A              Preserve ACLs

-X              Preserve extended attributes

--delete        Delete destination files that no longer exist in the source

--numeric-ids   Preserve numeric user and group IDs

Use --delete carefully. It keeps the destination as a mirror of the source. That is useful for a clean local mirror, but it also means deletions in /data/ will eventually be reflected in /data-backup/.

Create the rsync Backup Script

Create the script:

sudo nano /usr/local/sbin/rsync-backup-data.sh

Paste:

#!/bin/bash

 

SOURCE="/data/"

DESTINATION="/data-backup/"

LOGFILE="/var/log/rsync-data-backup.log"

LOCKFILE="/run/lock/rsync-data-backup.lock"

 

{

    echo "============================================================"

    echo "rsync backup started: $(date)"

    echo "Source: ${SOURCE}"

    echo "Destination: ${DESTINATION}"

    echo "============================================================"

} >> "$LOGFILE"

 

/usr/bin/flock -n "$LOCKFILE"     /usr/bin/nice -n 10 /usr/bin/ionice -c2 -n7     /usr/bin/rsync -aHAX --delete --numeric-ids     --info=stats2     "$SOURCE" "$DESTINATION" >> "$LOGFILE" 2>&1

 

EXIT_CODE=$?

 

{

    echo "============================================================"

    echo "rsync backup finished: $(date)"

    echo "Exit code: ${EXIT_CODE}"

    echo "============================================================"

    echo

} >> "$LOGFILE"

 

exit "$EXIT_CODE"

Make it executable:

sudo chmod 750 /usr/local/sbin/rsync-backup-data.sh

Test it manually:

sudo /usr/local/sbin/rsync-backup-data.sh

sudo tail -n 50 /var/log/rsync-data-backup.log

Optional Safety Check for a Mounted Backup Disk

If /data-backup/ is expected to be a separate mounted filesystem, add a mount check to the script before the rsync command:

if ! /usr/bin/mountpoint -q /data-backup; then

    echo "ERROR: /data-backup is not a mount point. Backup aborted: $(date)" >> "$LOGFILE"

    exit 2

fi

This prevents accidentally filling the root filesystem if an external or secondary disk is not mounted.

Schedule the rsync Backup with systemd

Create the service:

sudo nano /etc/systemd/system/rsync-data-backup.service

Paste:

[Unit]

Description=Scheduled rsync backup of /data to /data-backup

After=local-fs.target

 

[Service]

Type=oneshot

ExecStart=/usr/local/sbin/rsync-backup-data.sh

Create the timer:

sudo nano /etc/systemd/system/rsync-data-backup.timer

Paste:

[Unit]

Description=Run scheduled rsync backup of /data to /data-backup

 

[Timer]

OnCalendar=*-*-* 01:00:00

Persistent=true

Unit=rsync-data-backup.service

 

[Install]

WantedBy=timers.target

This schedules the backup for 1:00 AM. The ClamAV scan example runs at 2:00 AM, so the backup finishes first and the scan runs afterward.

Enable the timer:

sudo systemctl daemon-reload

sudo systemctl enable --now rsync-data-backup.timer

Check both scheduled jobs:

systemctl list-timers --all | egrep 'rsync|clamav'

Check the backup service:

systemctl status rsync-data-backup.timer

systemctl status rsync-data-backup.service

View backup logs:

sudo tail -f /var/log/rsync-data-backup.log

Backup First or Scan First?

A reasonable nightly schedule looks like this:

1:00 AM   rsync /data/ to /data-backup/

2:00 AM   ClamAV scan of /data/

This order gives the server a fresh backup before the antivirus scan runs. Another valid approach is to scan first and back up afterward, so detected files are not copied into the backup. There is no single perfect answer. For a home server, the most important point is that both jobs run consistently and produce logs.

A more cautious design is:

1:00 AM   ClamAV scan of /data/

2:00 AM   rsync /data/ to /data-backup/

That approach reduces the chance of copying suspicious files to the backup. However, if the scan takes a long time, the backup may overlap unless locks and scheduling are planned carefully.

Quarantine Instead of Delete

At first, avoid automatic deletion. A quarantine workflow is safer.

Create a quarantine directory:

sudo mkdir -p /var/quarantine/clamav

sudo chown clamav:clamav /var/quarantine/clamav

sudo chmod 700 /var/quarantine/clamav

To move detected files into quarantine, modify the scan command in the ClamAV script:

/usr/bin/clamdscan -v --fdpass --multiscan --move=/var/quarantine/clamav --log="$LOGFILE" "$SCAN_TARGET"

Avoid --remove until the behavior is well understood. The clamdscan documentation includes removal and quarantine-style options, but automatic deletion on a shared file server can create unnecessary operational risk (Canonical, n.d.-b).

Large Directories Require Patience

A large file share may appear to scan slowly. That is normal. File count matters as much as total size.

Estimate the size:

sudo du -sh /data/

sudo find /data/ -type f | wc -l

Watch ClamAV activity:

ps -eo pid,ppid,user,stat,pcpu,pmem,etime,cmd | egrep 'clamd|clamdscan'

sudo tail -f /var/log/clamav/scheduled-data-scan.log

Watch rsync backup activity:

ps -eo pid,ppid,user,stat,pcpu,pmem,etime,cmd | egrep 'rsync|rsync-backup'

sudo tail -f /var/log/rsync-data-backup.log

For disk I/O:

sudo apt install iotop

sudo iotop -oPa

Optional clamd Tuning

Check key daemon settings:

grep -E '^(MaxThreads|MaxQueue|MaxScanSize|MaxFileSize|MaxRecursion|LogFile|LocalSocket|User)' /etc/clamav/clamd.conf

For a modest home server:

MaxThreads 4

MaxQueue 100

For a larger server with more CPU and RAM:

MaxThreads 8

MaxQueue 200

Restart the daemon after changes:

sudo systemctl restart clamav-daemon

MaxThreads and related scan behavior are part of clamd.conf, which controls daemon-side scanning rather than the clamdscan command alone (Canonical, n.d.-a).

Should On-Access Scanning Be Enabled?

ClamAV supports on-access scanning on Linux through ClamOnAcc. ClamAV documentation describes this as real-time protection that can scan files when they are accessed and can optionally block access while scanning occurs (Cisco Talos, n.d.-c).

For a home file server, scheduled scanning is usually the better first step. It is easier to understand, easier to log, easier to troubleshoot, and less likely to interfere with Samba or nginx performance. On-access scanning can be added later after permissions, scan speed, and logging are understood.

Administration Cheat Sheet

Check ClamAV services:

systemctl status clamav-daemon

systemctl status clamav-freshclam

Update signatures manually:

sudo freshclam

Reload signatures into the daemon:

sudo clamdscan --reload

Run a manual scan:

sudo clamdscan -v --fdpass --multiscan --log=/var/log/clamav/manual-data-scan.log /data/

Check ClamAV timer:

systemctl list-timers --all | grep clamav

systemctl status clamav-data-scan.timer

systemctl status clamav-data-scan.service

Run a manual rsync backup:

sudo /usr/local/sbin/rsync-backup-data.sh

Check rsync timer:

systemctl list-timers --all | grep rsync

systemctl status rsync-data-backup.timer

systemctl status rsync-data-backup.service

View logs:

sudo tail -f /var/log/clamav/scheduled-data-scan.log

sudo tail -f /var/log/rsync-data-backup.log

sudo journalctl -u clamav-daemon -f

Final Recommendation

For a Linux Mint 21.3 home file server that shares /data/ through Samba to Windows 11 clients and through nginx over HTTP, the most maintainable design is:

·         Install ClamAV and clamav-daemon.

·         Run freshclam automatically.

·         Run clamd automatically.

·         Use clamdscan with --fdpass and --multiscan.

·         Scan /data/ on a systemd timer.

·         Back up /data/ to /data-backup/ with rsync on a systemd timer.

·         Log both scans and backups.

·         Quarantine suspicious files before considering deletion.

·         Keep Samba, nginx, ClamAV, and backup permissions explicit.

This gives the server a practical security and recovery baseline. ClamAV helps detect known malware in the shared repository. rsync provides a local backup mirror. Samba and nginx continue serving the same data through their normal paths. The result is a home file server that remains simple, visible, and maintainable.

References

Canonical. (n.d.-a). clamd.conf(5): Configuration file for Clam AntiVirus daemon. Ubuntu Manpages. https://manpages.ubuntu.com/manpages/jammy/man5/clamd.conf.5.html

Canonical. (n.d.-b). clamdscan(1): Scan files and directories for viruses using Clam AntiVirus daemon. Ubuntu Manpages. https://manpages.ubuntu.com/manpages/jammy/man1/clamdscan.1.html

Cisco Talos. (n.d.-a). ClamAV documentation: Introduction. ClamAV Documentation. https://docs.clamav.net/

Cisco Talos. (n.d.-b). ClamAV documentation: Scanning. ClamAV Documentation. https://docs.clamav.net/manual/Usage/Scanning.html

Cisco Talos. (n.d.-c). ClamAV documentation: On-access scanning. ClamAV Documentation. https://docs.clamav.net/manual/OnAccess.html

freedesktop.org. (n.d.). systemd.timer: Timer unit configuration. https://www.freedesktop.org/software/systemd/man/systemd.timer.html

Linux Mint. (n.d.). New features in Linux Mint 21.3 Virginia. https://www.linuxmint.com/rel_virginia_whatsnew.php

NGINX. (n.d.). Serving static content. NGINX Documentation. https://docs.nginx.com/nginx/admin-guide/web-server/serving-static-content/

rsync project. (n.d.). rsync(1): Linux man page. https://download.samba.org/pub/rsync/rsync.1

Samba Team. (n.d.). vfs_virusfilter: On access virus scanner. Samba Documentation. https://www.samba.org/samba/docs/current/man-html/vfs_virusfilter.8.h