Froxlor is a lightweight, open-source server management panel designed for Linux system administrators and hosting providers. Built with PHP and supporting multiple web servers (Apache, Nginx, FPM), it manages domains, databases, email accounts, and SSL certificates. While actively maintained, Froxlor requires comprehensive security hardening to protect server infrastructure and customer data.
Froxlor supports TOTP-based 2FA for admin and customer accounts. Enable it immediately after installation.
Enable 2FA via Froxlor UI:
Force 2FA for all admins via database:
-- Login to MySQL/MariaDB
mysql -u root -p froxlor
-- Enable 2FA enforcement for admins (Froxlor 2.x+)
UPDATE panel_admins SET twofa_enabled = '1' WHERE twofa_enabled = '0';
-- Verify setting
SELECT loginname, twofa_enabled FROM panel_admins;
Change default admin credentials immediately:
# Froxlor doesn't have a default password - it's set during installation
# But you should create a new admin and disable the installation account
# Via Froxlor UI: Admin → Admins → Add Admin
Best practices:
Create admin via CLI (Froxlor 2.x+):
cd /var/www/froxlor
php bin/froxlor-console admin:add --username=newadmin --email=admin@example.com --role=1
Configure customer permissions:
Key customer restrictions:
| Setting | Recommended | Reason |
|---|---|---|
| Max domains | Based on plan | Prevent resource abuse |
| Max FTP accounts | 5-10 per domain | Limit access points |
| Max databases | Based on plan | Prevent database sprawl |
| PHP access | Restricted versions | Security consistency |
| SSH access | Disabled by default | Reduce attack surface |
Restrict panel access by IP:
# Using iptables
sudo iptables -A INPUT -p tcp --dport 443 -s 10.0.0.0/24 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 443 -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="443" protocol="tcp" accept'
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" port port="443" protocol="tcp" reject'
sudo firewall-cmd --reload
# Using UFW
sudo ufw allow from 10.0.0.0/24 to any port 443
sudo ufw deny 443
sudo ufw enable
Configure via Froxlor:
Configure session settings in Froxlor:
300 seconds (5 minutes for admin), 900 for customersEdit /var/www/froxlor/lib/config.php (if not set via UI):
<?php
// Session configuration
$settings['session_timeout'] = 300; // 5 minutes for admins
$settings['session_timeout_customers'] = 900; // 15 minutes for customers
$settings['session_allow_multiple'] = false; // One session per user
?>
Secure session handling in /etc/php/*/apache2/php.ini or /etc/php/*/fpm/php.ini:
[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
Froxlor can manage its own SSL certificates via Let’s Encrypt:
Option 1: Auto-generate via Froxlor (Recommended)
Option 2: Manual Let’s Encrypt
sudo apt install certbot # Debian/Ubuntu
sudo certbot certonly --standalone -d froxlor.example.com
# Link certificates
sudo cp /etc/letsencrypt/live/froxlor.example.com/fullchain.pem /etc/ssl/certs/froxlor.crt
sudo cp /etc/letsencrypt/live/froxlor.example.com/privkey.pem /etc/ssl/private/froxlor.key
sudo chmod 600 /etc/ssl/private/froxlor.key
Option 3: Self-signed certificate (testing only)
sudo mkdir -p /etc/ssl/private
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/ssl/private/froxlor.key \
-out /etc/ssl/certs/froxlor.crt \
-subj "/C=US/ST=State/L=City/O=Organization/CN=froxlor.example.com"
sudo chmod 600 /etc/ssl/private/froxlor.key
Configure web server for HTTPS:
Apache (/etc/apache2/sites-available/froxlor.conf):
<VirtualHost *:443>
ServerName froxlor.example.com
DocumentRoot /var/www/froxlor
SSLEngine on
SSLCertificateFile /etc/ssl/certs/froxlor.crt
SSLCertificateKeyFile /etc/ssl/private/froxlor.key
SSLCertificateChainFile /etc/ssl/certs/chain.pem
# Security headers
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-XSS-Protection "1; mode=block"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
<Directory /var/www/froxlor>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
# Redirect HTTP to HTTPS
<VirtualHost *:80>
ServerName froxlor.example.com
Redirect permanent / https://froxlor.example.com/
</VirtualHost>
Nginx (/etc/nginx/sites-available/froxlor):
server {
listen 443 ssl;
server_name froxlor.example.com;
root /var/www/froxlor;
index index.php index.html;
ssl_certificate /etc/ssl/certs/froxlor.crt;
ssl_certificate_key /etc/ssl/private/froxlor.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers on;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\. {
deny all;
}
}
server {
listen 80;
server_name froxlor.example.com;
return 301 https://$server_name$request_uri;
}
Default Froxlor ports:
| Port | Service | Required |
|---|---|---|
| 80 | HTTP (redirect to HTTPS) | Yes |
| 443 | HTTPS (Froxlor UI) | 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) |
Configure CSF firewall:
# Install CSF
cd /usr/src
wget https://download.configserver.com/csf.tgz
tar -xzf csf.tgz
cd csf
sh install.sh
# Edit /etc/csf/csf.conf
TCP_IN = "20,21,22,25,53,80,110,143,443,465,587,993,995"
TCP_OUT = "20,21,25,53,80,110,443,465,587"
# Enable port scan protection
PS_INTERVAL = "60"
# Enable logging
LOG_INTERVAL = "60"
# Restart CSF
csf -r
Verify firewall status:
csf -s # Show current rules
csf -l # List blocked IPs
Configure fail2ban for Froxlor:
sudo apt install fail2ban # Debian/Ubuntu
sudo dnf install fail2ban # RHEL/Fedora
Create Froxlor jail (/etc/fail2ban/jail.local):
[froxlor]
enabled = true
port = http,https
filter = froxlor
logpath = /var/www/froxlor/logs/*.log
maxretry = 5
bantime = 3600
findtime = 300
[froxlor-admin]
enabled = true
port = http,https
filter = froxlor-admin
logpath = /var/www/froxlor/logs/*.log
maxretry = 3
bantime = 7200
findtime = 300
Create filter (/etc/fail2ban/filter.d/froxlor.conf):
[Definition]
failregex = ^.*Login failed.*<HOST>.*$
^.*Failed login attempt.*<HOST>.*$
^.*Authentication failure.*<HOST>.*$
^.*Invalid.*password.*<HOST>.*$
ignoreregex =
Create admin filter (/etc/fail2ban/filter.d/froxlor-admin.conf):
[Definition]
failregex = ^.*Admin login failed.*<HOST>.*$
^.*Admin authentication failure.*<HOST>.*$
ignoreregex =
Restart fail2ban:
sudo systemctl restart fail2ban
Edit /var/www/froxlor/lib/config.php:
<?php
// Security settings
// Disable debug in production
$settings['debug'] = false;
// Session security
$settings['session_timeout'] = 300;
$settings['session_allow_multiple'] = false;
// Password requirements
$settings['password_min_length'] = 12;
$settings['password_require_special'] = true;
$settings['password_require_numbers'] = true;
// Login attempt limits
$settings['login_attempts'] = 5;
$settings['login_lockout_time'] = 300; // 5 minutes
// File upload limits
$settings['max_upload_size'] = 10; // MB
?>
Froxlor 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
# Froxlor-specific: use dedicated database user
Create restricted Froxlor database user:
-- Login to MySQL
mysql -u root -p
-- Create dedicated user with minimal privileges
CREATE USER 'froxlor'@'localhost' IDENTIFIED BY 'strong-password-here';
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, CREATE TEMPORARY TABLES ON froxlor.* TO 'froxlor'@'localhost';
FLUSH PRIVILEGES;
-- Do NOT grant: FILE, PROCESS, SUPER, RELOAD, SHUTDOWN, GRANT OPTION
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,passthru,leak,fopen,readfile
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 = 20M
upload_max_filesize = 20M
max_file_uploads = 5
[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
[Open Basedir]
open_basedir = /var/www/froxlor:/tmp:/var/tmp
Restart web server:
sudo systemctl restart apache2 # Apache
sudo systemctl restart php*-fpm # PHP-FPM
sudo systemctl restart nginx # Nginx
Secure Froxlor installation:
# Froxlor typically installs to /var/www/froxlor
sudo chown -R froxlor:froxlor /var/www/froxlor
sudo chmod -R 755 /var/www/froxlor
# Config file should be more restrictive
sudo chmod 640 /var/www/froxlor/lib/config.php
# Logs directory
sudo chmod 750 /var/www/froxlor/logs
sudo chown froxlor:adm /var/www/froxlor/logs
# Templates directory (if writable)
sudo chmod 755 /var/www/froxlor/templates
Protect sensitive files:
# /etc/apache2/conf-available/froxlor-hardening.conf
<FilesMatch "(config\.php|settings\.bak|\.sql|\.log)$">
Require all denied
</FilesMatch>
# Prevent directory listing
<Directory /var/www/froxlor>
Options -Indexes
</Directory>
Froxlor uses cron for automated tasks. Secure it:
# Edit Froxlor cron job
sudo crontab -e
# Standard Froxlor cron (runs every minute)
* * * * * /usr/bin/php /var/www/froxlor/scripts/froxlor-master-cron.php
# Ensure cron directory is protected
chmod 700 /etc/cron.d
chmod 600 /etc/crontab
Restrict cron access:
# /etc/cron.allow - only root and froxlor user
echo "root" >> /etc/cron.allow
echo "froxlor" >> /etc/cron.allow
# /etc/cron.deny - deny all others
echo "ALL" >> /etc/cron.deny
Update Froxlor:
# Via apt (Debian/Ubuntu with Froxlor repository)
sudo apt update
sudo apt upgrade froxlor
# Via manual update
cd /var/www/froxlor
wget https://github.com/froxlor/Froxlor/releases/download/latest/froxlor.zip
unzip -o froxlor.zip
chown -R froxlor:froxlor /var/www/froxlor
chmod -R 755 /var/www/froxlor
# Run database upgrade
php bin/froxlor-console upgrade
Enable automatic security updates:
# Install unattended-upgrades
sudo apt install unattended-upgrades
# Configure for Froxlor
sudo dpkg-reconfigure --priority=low unattended-upgrades
Check current version:
cd /var/www/froxlor
php bin/froxlor-console version
Froxlor log locations:
| Log File | Purpose |
|---|---|
/var/www/froxlor/logs/ |
Panel operation logs |
/var/www/froxlor/logs/error.log |
Panel errors |
/var/www/froxlor/logs/access.log |
Panel access |
/var/log/mysql/error.log |
Database errors |
/var/log/apache2/froxlor* |
Web server logs |
/var/log/froxlor/ |
System logs (if configured) |
View recent activity:
# Froxlor logs
tail -f /var/www/froxlor/logs/*.log
# Filter for login events
grep -i "login\|auth" /var/www/froxlor/logs/*.log
# Failed login attempts
grep -i "failed\|invalid" /var/www/froxlor/logs/*.log | tail -50
# Cron execution
grep "CRON" /var/www/froxlor/logs/*.log
Set up alerts for:
Configure log rotation:
# /etc/logrotate.d/froxlor
/var/www/froxlor/logs/*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
create 640 froxlor adm
sharedscripts
postrotate
systemctl reload rsyslog > /dev/null 2>&1 || true
endscript
}
Rsyslog configuration:
# /etc/rsyslog.d/froxlor.conf
:filename, contains, "froxlor" /var/log/froxlor/central.log
:filename, contains, "froxlor" @syslog.example.com:514
# Restart rsyslog
sudo systemctl restart rsyslog
Filebeat for ELK stack:
# /etc/filebeat/modules.d/froxlor.yml
- module: froxlor
access:
enabled: true
var.paths: ["/var/www/froxlor/logs/access.log"]
error:
enabled: true
var.paths: ["/var/www/froxlor/logs/error.log"]
Enable MySQL audit logging:
# Install audit plugin (MariaDB)
sudo apt install mariadb-plugin-server-audit
# Configure /etc/mysql/mariadb.conf.d/99-audit.cnf
[mysqld]
plugin_load_add = server_audit
server_audit_logging = ON
server_audit_events = CONNECT,QUERY,TABLE
server_audit_output_type = FILE
server_audit_file_path = /var/log/mysql/audit.log
Monitor database queries:
# View slow queries
tail -f /var/log/mysql/slow.log
# Check for suspicious patterns
grep -i "DROP\|DELETE\|TRUNCATE" /var/log/mysql/audit.log | tail -50
Froxlor provides a REST API for automation. Secure it properly:
Enable API access:
API security best practices:
Example API call:
curl -X POST https://froxlor.example.com/AdminAPI/ \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"command":"customers.list"}'
Restrict API by IP (/var/www/froxlor/lib/config.php):
<?php
$settings['api_allowed_ips'] = ['10.0.0.0/24', '192.168.1.0/24'];
?>
| Control | Status | Notes |
|---|---|---|
| 2FA enabled for all admins | ☐ | Enforce via settings |
| HTTPS with valid certificate | ☐ | Let’s Encrypt recommended |
| Firewall configured | ☐ | CSF with port restrictions |
| fail2ban deployed | ☐ | Froxlor jails configured |
| Database user restricted | ☐ | Minimal privileges only |
| PHP hardening applied | ☐ | Disable dangerous functions |
| File permissions secured | ☐ | Config 640, directories 755 |
| Cron jobs secured | ☐ | cron.allow configured |
| API keys secured | ☐ | If using API |
| Centralized logging | ☐ | Forward to SIEM |
| Regular updates scheduled | ☐ | Froxlor + system packages |
| Backup encryption enabled | ☐ | Encrypt off-server backups |
If you suspect a security breach:
Isolate the server
# Block external access
csf -d ALL # If using CSF
# Or via iptables
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/www/froxlor/logs /root/froxlor-logs-$(date +%Y%m%d-%H%M%S)
cp -r /var/log/apache2 /root/apache-logs-$(date +%Y%m%d-%H%M%S)
mysqldump -u root -p froxlor > /root/froxlor-db-$(date +%Y%m%d-%H%M%S).sql
Review recent activity
# Failed logins
grep -i "failed" /var/www/froxlor/logs/*.log | tail -100
# New accounts
mysql -u root -p froxlor -e "SELECT * FROM panel_customers ORDER BY inserttime DESC LIMIT 10"
# New domains
mysql -u root -p froxlor -e "SELECT * FROM panel_domains ORDER BY inserttime DESC LIMIT 10"
Check for unauthorized changes
# Compare file integrity
debsums -s froxlor # If installed via apt
# Look for recently modified files
find /var/www/froxlor -type f -mtime -7 -ls
Change all credentials - Admin passwords, API keys, database passwords, FTP accounts
Scan for malware
sudo apt install clamav clamav-daemon
sudo freshclam
sudo clamscan -r /var/www/froxlor --move=/home/quarantine
Patch vulnerabilities - Update Froxlor, PHP, web server, and all packages
Restore from clean backup - If compromise is severe
Notify affected users - If customer data was exposed