Virtualmin is a powerful open-source web hosting and cloud control panel built on Webmin. First released in 2005, it provides comprehensive management for web domains, email, databases, DNS, and virtual servers. Virtualmin’s modular architecture and deep system integration make it popular among system administrators, but this same access requires comprehensive security hardening to protect server infrastructure.
Virtualmin/Webmin supports TOTP-based 2FA and U2F (hardware keys). Enable it immediately after installation.
Enable 2FA via Virtualmin UI:
Enable U2F hardware key support:
Force 2FA for all users:
# Edit Webmin configuration
sudo nano /etc/webmin/miniserv.conf
# Add or modify:
forced_2fa=1
# Restart Webmin
sudo systemctl restart webmin
Secure the default root/admin account:
# Change root password
sudo passwd root
# Or via Webmin UI
# Webmin → Webmin Configuration → Change Password
Best practices:
Create new admin user:
# Via Webmin UI
# Webmin → Webmin Users → Create a new Webmin user
# Set username, password, and grant "All modules" access
# Or via CLI
/usr/share/webmin/adduser.pl newadmin 'StrongPassword123!'
Delete or disable unused accounts:
# Disable user via Webmin UI
# Webmin → Webmin Users → Select user → Disable
# Or via CLI
/usr/share/webmin/deleteuser.pl oldadmin
Restrict panel access by IP using Webmin’s built-in feature:
# Edit Webmin configuration
sudo nano /etc/webmin/miniserv.conf
# Add allowed IPs (comma-separated)
allow=10.0.0.0/24,192.168.1.0/24
# Or deny specific IPs
deny=192.168.100.100,192.168.100.101
# Restart Webmin
sudo systemctl restart webmin
Configure via Webmin UI:
Using iptables:
# Allow only from management network
sudo iptables -A INPUT -p tcp --dport 10000 -s 10.0.0.0/24 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 10000 -j DROP
sudo iptables-save > /etc/iptables/rules.v4
Using firewalld:
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.0/24" port port="10000" protocol="tcp" accept'
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" port port="10000" protocol="tcp" reject'
sudo firewall-cmd --reload
Using UFW:
sudo ufw allow from 10.0.0.0/24 to any port 10000
sudo ufw deny 10000
sudo ufw enable
Configure session settings in Webmin:
300 seconds (5 minutes for admin)Edit /etc/webmin/miniserv.conf:
# Session timeout (seconds)
logout_timeout=300
# Single session
single_session=1
# Referrer checking
no_referrer=0
# Secure cookies
cookie_secure=1
cookie_httponly=1
Restart Webmin:
sudo systemctl restart webmin
Configure password requirements:
# Edit Webmin configuration
sudo nano /etc/webmin/miniserv.conf
# Password policy settings
passwd_mode=1 # Require strong passwords
passwd_length=12 # Minimum length
Enforce password history (prevent reuse):
# Install pam_pwhistory
sudo apt install libpam-pwquality # Debian/Ubuntu
sudo dnf install libpwquality # RHEL/CentOS
# Configure /etc/pam.d/common-password
password required pam_pwhistory.so use_authtok remember=12 enforce_for_root
password required pam_unix.so use_authtok sha512
Virtualmin/Webmin uses HTTPS by default on port 10000. Replace self-signed certificate with valid certificate:
Option 1: Let’s Encrypt via Virtualmin (Recommended)
# Via Virtualmin UI
# Virtualmin → Server Configuration → SSL Certificate → Let's Encrypt
# Request certificate for your hostname
# Or via CLI
/usr/share/virtualmin/request-letsencrypt.pl --domain virtualmin.example.com --email admin@example.com
Option 2: Manual Let’s Encrypt
sudo apt install certbot
# Generate certificate
sudo certbot certonly --standalone -d virtualmin.example.com
# Link to Webmin
sudo cp /etc/letsencrypt/live/virtualmin.example.com/fullchain.pem /etc/webmin/miniserv.pem
sudo cp /etc/letsencrypt/live/virtualmin.example.com/privkey.pem /etc/webmin/miniserv.pem
cat /etc/letsencrypt/live/virtualmin.example.com/privkey.pem >> /etc/webmin/miniserv.pem
# Restart Webmin
sudo systemctl restart webmin
Option 3: Self-signed certificate (testing only)
sudo /usr/share/webmin/restart.pl --force-new-certs
Configure SSL/TLS settings:
# Edit /etc/webmin/miniserv.conf
ssl=1
ssl_min_version=TLSv1.2
ssl_ciphers=ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
# Redirect HTTP to HTTPS
redirect_ssl=1
Default Virtualmin/Webmin ports:
| Port | Service | Required |
|---|---|---|
| 10000 | Webmin/Virtualmin Admin Panel | Yes |
| 80 | HTTP (web domains) | Yes |
| 443 | HTTPS (web domains) | Yes |
| 21 | FTP | If FTP enabled |
| 22 | SSH | Recommended |
| 25 | SMTP | If mail enabled |
| 53 | DNS | If DNS enabled |
| 110/995 | POP3 | If mail enabled |
| 143/993 | IMAP | If mail enabled |
| 3306 | MySQL | No (localhost only) |
Virtualmin includes built-in firewall management:
Configure via Virtualmin UI:
Configure CSF (if installed):
# Edit /etc/csf/csf.conf
TCP_IN = "20,21,22,25,53,80,110,143,443,465,587,993,995,10000"
TCP_OUT = "20,21,25,53,80,110,443,465,587"
# Enable port scan protection
PS_INTERVAL = "60"
# Restart CSF
csf -r
Webmin includes built-in fail2ban integration:
Enable fail2ban protection:
# Install fail2ban (if not already installed)
sudo apt install fail2ban # Debian/Ubuntu
sudo dnf install fail2ban # RHEL/CentOS
Webmin fail2ban jails (/etc/fail2ban/jail.local):
[webmin-auth]
enabled = true
port = 10000
filter = webmin-auth
logpath = /var/log/webmin/auth.log
maxretry = 5
bantime = 3600
findtime = 300
[webmin-bruteforce]
enabled = true
port = 10000
filter = webmin-bruteforce
logpath = /var/log/webmin/auth.log
maxretry = 3
bantime = 7200
findtime = 300
Create Webmin filter (/etc/fail2ban/filter.d/webmin-auth.conf):
[Definition]
failregex = ^.*Failed login.*<HOST>.*$
^.*Authentication failure.*<HOST>.*$
^.*Invalid.*password.*<HOST>.*$
^.*Login failed.*<HOST>.*$
ignoreregex =
Restart fail2ban:
sudo systemctl restart fail2ban
View banned IPs:
fail2ban-client status webmin-auth
fail2ban-client set webmin-auth unbanip <IP>
Configure Webmin rate limiting:
# Edit /etc/webmin/miniserv.conf
# Add rate limiting
max_connections=100
max_connections_per_ip=10
# Restart Webmin
sudo systemctl restart webmin
Configure iptables rate limiting:
# Rate limit connections to Webmin
sudo iptables -A INPUT -p tcp --dport 10000 -m state --state NEW -m recent --set
sudo iptables -A INPUT -p tcp --dport 10000 -m state --state NEW -m recent --update --seconds 60 --hitcount 10 -j DROP
sudo iptables-save > /etc/iptables/rules.v4
Edit /etc/webmin/miniserv.conf:
# Security settings
# Disable debug in production
log=1
# Session security
logout_timeout=300
single_session=1
# SSL/TLS settings
ssl=1
ssl_min_version=TLSv1.2
# Login attempt limits
login_failures=5
login_lock_time=300
# File upload limits
max_upload=10485760 # 10MB
Apply configuration:
sudo systemctl restart webmin
Webmin has many modules. Disable unused ones:
# List installed modules
ls /usr/share/webmin/
# Disable unused modules via Webmin UI
# Webmin → Webmin Configuration → Modules
# Or edit /etc/webmin/installed.config
# Remove or comment out unused modules
Recommended modules for minimal install:
Virtualmin uses MySQL/MariaDB. Secure it:
# Run secure installation
sudo mysql_secure_installation
# Key settings:
# - Set root password
# - Remove anonymous users
# - Disallow root login remotely
# - Remove test database
Configure /etc/mysql/mariadb.conf.d/50-server.cnf:
[mysqld]
# Network security - bind to localhost only
bind-address = 127.0.0.1
skip-networking = 1
# Disable local infile
local-infile = 0
# Secure file handling
secure_file_priv = /var/lib/mysql-files
# Logging
log_error = /var/log/mysql/error.log
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2
Edit /etc/php/*/apache2/php.ini or /etc/php/*/fpm/php.ini:
[Security]
expose_php = Off
allow_url_fopen = Off
allow_url_include = Off
disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source
display_errors = Off
log_errors = On
error_reporting = E_ALL
html_errors = Off
[Resource Limits]
max_execution_time = 30
max_input_time = 60
memory_limit = 256M
post_max_size = 64M
upload_max_filesize = 64M
max_file_uploads = 20
[Session]
session.cookie_httponly = 1
session.cookie_secure = 1
session.use_only_cookies = 1
session.cookie_samesite = Strict
session.use_strict_mode = 1
session.gc_maxlifetime = 900
Restart web server:
sudo systemctl restart apache2 # Apache
sudo systemctl restart php*-fpm # PHP-FPM
Secure Webmin installation:
# Webmin directory permissions
sudo chown -R root:root /usr/share/webmin
sudo chmod -R 755 /usr/share/webmin
sudo chmod 600 /etc/webmin/miniserv.conf
# Webmin data directory
sudo chmod 700 /var/webmin
sudo chown root:root /var/webmin
# SSL certificates
sudo chmod 600 /etc/webmin/miniserv.pem
Protect sensitive files:
# /etc/apache2/conf-available/webmin-hardening.conf
<FilesMatch "(miniserv\.conf|\.sql|\.log|\.bak|\.old)$">
Require all denied
</FilesMatch>
Update Virtualmin/Webmin:
# Update via apt (Debian/Ubuntu)
sudo apt update
sudo apt upgrade virtualmin webmin
# Update via yum/dnf (RHEL/CentOS)
sudo dnf update virtualmin webmin
# Or via Webmin UI
# Webmin → Webmin Configuration → Check and Install Updates
Enable automatic security updates:
# Install unattended-upgrades
sudo apt install unattended-upgrades
# Configure
sudo dpkg-reconfigure --priority=low unattended-upgrades
Webmin/Virtualmin log locations:
| Log File | Purpose |
|---|---|
/var/log/webmin/ |
Main panel logs |
/var/log/webmin/auth.log |
Authentication logs |
/var/log/webmin/miniserv.log |
Webmin server logs |
/var/log/virtualmin/ |
Virtualmin operation logs |
/var/log/apache2/ |
Apache access/error logs |
/var/log/mysql/error.log |
Database errors |
View recent activity:
# Webmin logs
tail -f /var/log/webmin/auth.log
# Filter for login events
grep -i "login\|auth" /var/log/webmin/auth.log
# Failed login attempts
grep -i "failed\|invalid" /var/log/webmin/auth.log | tail -50
# Virtualmin logs
tail -f /var/log/virtualmin/*.log
Set up alerts for:
Configure log rotation:
# /etc/logrotate.d/webmin
/var/log/webmin/*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
create 640 root adm
}
Rsyslog configuration:
# /etc/rsyslog.d/webmin.conf
:filename, contains, "webmin" /var/log/webmin/central.log
:filename, contains, "webmin" @syslog.example.com:514
# Restart rsyslog
sudo systemctl restart rsyslog
Install AIDE to detect file changes:
# Install AIDE
sudo apt install aide
# Initialize database
sudo aideinit
# Configure Webmin/Virtualmin paths in /etc/aide/aide.conf
/usr/share/webmin/ p+i+n+u+g+s+m+c+acl+selinux+xattrs+sha512
/etc/webmin/ p+i+n+u+g+s+m+c+acl+selinux+xattrs+sha512
/var/log/webmin/ p+i+n+u+g+s+m+c+acl+selinux+xattrs+sha512
# Schedule daily checks
0 5 * * * /usr/bin/aide --check
Webmin provides API access. Secure it properly:
Enable API access:
# Edit /etc/webmin/miniserv.conf
# API is enabled by default on port 10000
# Restrict API by IP
allow=10.0.0.0/24
API security best practices:
Example API call:
curl -k -u admin:password \
https://virtualmin.example.com:10000/virtualmin-cli \
--data '{"command": "list-domains"}'
| Control | Status | Notes |
|---|---|---|
| 2FA enabled for all admins | ☐ | Via authentication settings |
| HTTPS with valid certificate | ☐ | Let’s Encrypt recommended |
| Firewall configured | ☐ | Virtualmin firewall + CSF |
| fail2ban deployed | ☐ | Webmin jails configured |
| Database hardened | ☐ | mysql_secure_installation |
| PHP hardening applied | ☐ | Disable dangerous functions |
| File permissions secured | ☐ | Config 600, directories 755 |
| Unused modules disabled | ☐ | Reduce attack surface |
| Centralized logging | ☐ | Forward to SIEM |
| Regular updates scheduled | ☐ | Virtualmin + system packages |
| File integrity monitoring | ☐ | AIDE configured |
If you suspect a security breach:
Isolate the server
# Block external access
sudo iptables -P INPUT DROP
sudo iptables -A INPUT -i lo -j ACCEPT
sudo iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
Preserve evidence
cp -r /var/log/webmin /root/webmin-logs-$(date +%Y%m%d-%H%M%S)
cp -r /var/log/virtualmin /root/virtualmin-logs-$(date +%Y%m%d-%H%M%S)
mysqldump -u root -p --all-databases > /root/all-db-$(date +%Y%m%d-%H%M%S).sql
Review recent activity
# Failed logins
grep -i "failed" /var/log/webmin/auth.log | tail -100
# Check new accounts
/usr/share/webmin/list-users.pl
# Check new domains
/usr/share/virtualmin/virtualmin.pl list-domains
Check for unauthorized changes
# Compare with AIDE database
sudo aide --check
# Look for recently modified files
find /usr/share/webmin -type f -mtime -7 -ls
Change all credentials - Admin passwords, database passwords, API keys
Scan for malware
sudo apt install clamav clamav-daemon
sudo freshclam
sudo clamscan -r /var/www --move=/home/quarantine
Patch vulnerabilities - Update Virtualmin, Webmin, and all packages
Restore from clean backup - If compromise is severe
Notify affected users - If customer data was exposed