FTP, FTPS, or SFTP on cpanel/directadmin: correct architecture and common failures
Back to blog

FTP, FTPS, or SFTP on cpanel/directadmin: correct architecture and common failures

6/7/2026 · 5 min · Infrastructure

This is a remarkably common support incident in managed hosting environments: an additional FTP account connects perfectly via FileZilla using FTPS but repeatedly fails on SFTP with a "Permission denied" error. Support teams often treat this as a simple "incorrect password" issue and reset the credentials, but the forensic reality points toward an authentication architecture conflict, not a typo.


1. Real system users vs. virtual daemon users#

To diagnose this failure, it is essential to understand how Linux handles connections for each protocol:

Takeaway: SSH has no knowledge of virtual FTP accounts and cannot authenticate them. By design, additional FTP accounts created in admin panels cannot connect via SFTP on port 22.


2. Operational diagnostics: validating the FTP service#

If clients cannot connect even using FTPS, follow this terminal diagnostics workflow to verify the daemon's status:

# 1. Verify if the FTP daemon is active (Pure-FTPd or ProFTPD)
systemctl status pure-ftpd
# or
systemctl status proftpd

# 2. Check if the daemon is listening on the default port 21 or 990 (Implicit FTPS)
ss -lntp | grep -E "21|990"

# 3. Test local responsiveness on port 21
nc -zv localhost 21
# or
telnet localhost 21

# 4. Check if the user is virtual or a system-level account
getent passwd ftpuser || echo "Virtual account detected: does not exist in /etc/passwd"

3. Explicit FTPS (port 21) vs. implicit FTPS (port 990)#

FTP over TLS encryption can be established in two different modes:

How to test TLS connections via command line:#

# Test Explicit TLS negotiation (Port 21)
openssl s_client -connect domain.com:21 -starttls ftp

# Test Implicit TLS connection (Port 990)
openssl s_client -connect domain.com:990

4. Security hardening in pure-ftpd and proftpd#

Default FTP daemon configurations are insecure. You should enforce strict encryption rules and resource usage limits.

Disabling plain FTP (enforcing TLS criptography)#

Plaintext connections expose credentials to sniffing attacks. The daemon must be configured to reject unencrypted links.

On Pure-FTPd (standard on cPanel/DirectAdmin):

# Edit the TLS configuration file
# Values: 0 (No TLS), 1 (Optional TLS), 2 (Force TLS)
echo "2" | sudo tee /etc/pure-ftpd/conf/TLS

# Restart the service
sudo systemctl restart pure-ftpd

On ProFTPD:

# Edit /etc/proftpd/proftpd.conf or /etc/proftpd/conf.d/tls.conf
# Add or modify the mod_tls block:
<IfModule mod_tls.c>
    TLSEngine on
    TLSRequired on
</IfModule>

# Restart the service
sudo systemctl restart proftpd

Restricting weak ciphers in FTPS#

Ensure only robust encryption algorithms are negotiated during the handshake.

On Pure-FTPd:

# Set strong ciphers in the configuration file
echo "HIGH:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK:!RC4" | sudo tee /etc/pure-ftpd/conf/TLSCipherSuite

# Restart the service
sudo systemctl restart pure-ftpd

Limiting connection attempts (brute-force prevention)#

Protect the system against connection exhaustion and dictionary attacks.

On Pure-FTPd:

# Limit the maximum number of connections per IP address (e.g., 3)
echo "3" | sudo tee /etc/pure-ftpd/conf/MaxClientsPerIP
sudo systemctl restart pure-ftpd

On ProFTPD:

# Add to your proftpd.conf file
MaxClientsPerHost 3
MaxClients 100

For SFTP (SSH) brute force protection, set up Fail2Ban by editing /etc/fail2ban/jail.local:

[sshd]
enabled = true
port = ssh
maxretry = 3
bantime = 3600

Apply rules with sudo systemctl restart fail2ban.


5. Configuring passive mode and firewall rules#

FTP uses distinct channels for command control (port 21) and data transfer (random ports provided by the server). On servers protected by firewalls or NAT, passive transfers will fail unless a specific port range is defined and opened.

Configure passive ports in pure-ftpd:#

# Define the passive port range (e.g., 30000 to 31000)
echo "30000 31000" | sudo tee /etc/pure-ftpd/conf/PassivePortRange

# If your server is behind NAT (e.g., AWS EC2 or Google Cloud), force the public external IP:
echo "SERVER_PUBLIC_IP" | sudo tee /etc/pure-ftpd/conf/ForcePassiveIP

# Restart the service
sudo systemctl restart pure-ftpd

Open the passive ports in UFW firewall:#

sudo ufw allow 21/tcp
sudo ufw allow 990/tcp
sudo ufw allow 30000:31000/tcp
sudo ufw reload

6. Creating a chrooted SFTP user (jailed shell)#

If your client strictly requires a secure SFTP connection for a third party while ensuring directory isolation, you must configure a real Linux system user with specific rules inside sshd_config.

# 1. Create a system user without shell access (/bin/false)
sudo useradd -m -s /bin/false sftpuser
sudo passwd sftpuser

# 2. Set home directory ownership to root (mandatory requirement for SFTP chroot)
sudo chown root:root /home/sftpuser
sudo chmod 755 /home/sftpuser

# 3. Create the writeable upload folder inside the jailed root
sudo mkdir -p /home/sftpuser/uploads
sudo chown sftpuser:sftpuser /home/sftpuser/uploads

Configure chroot rules in SSHD#

Edit the /etc/ssh/sshd_config file and add the following block at the bottom:

Match User sftpuser
    ForceCommand internal-sftp
    ChrootDirectory /home/%u
    AllowTcpForwarding no
    X11Forwarding no
    PermitTunnel no

Test SSH configurations for syntax errors before reloading:

sudo sshd -t
sudo systemctl restart sshd

7. FTP log mapping and client verification#

When debugging connection errors or failed login requests for virtual FTP users, check the daemon log files:

Testing FTP connectivity locally:#

# Test using curl (FTPS)
curl -v --ftp-ssl --user "[email protected]:password" ftp://domain.com:21/

# Test using lftp (forcing TLS)
lftp -u "[email protected],password" -e "set ftp:ssl-force on; ls; bye" domain.com

8. Control panel configurations (cPanel and DirectAdmin)#

If you run a control panel environment, service configurations should be managed via the panel UI to prevent custom changes from being overwritten during system updates.

Cpanel/whm configuration:#

  1. Navigate to WHM > Service Configuration > FTP Server Selection.
  2. Select Pure-FTPd (recommended) or ProFTPD.
  3. Go to WHM > FTP Server > TLS/SSL Management to configure certificates and enforce TLS connections.

DirectAdmin configuration:#

  1. Under the Admin level, go to Server Manager > FTP Server.
  2. Set daemon behaviors and verify that SSL/TLS is enabled.
  3. Configure passive port ranges in /etc/pure-ftpd.conf using DirectAdmin's secure file manager.

Technical comparison: FTP vs. FTPS vs. SFTP#

FeatureFTPFTPSSFTP
Underlying ProtocolStandard FTPFTP over TLSSSH (Secure Shell)
Default Port2121 (Explicit) / 990 (Implicit)22 (Or custom SSH port)
EncryptionNone (Plaintext)TLS/SSLNative SSH
AuthenticationVirtual or SystemVirtual or SystemSystem Users Only
Jail (Chroot)Native by daemonNative by daemonRequires manual configuration
Firewall SetupComplex (Passive range)Complex (Passive range)Simple (Single SSH port)
Recommendation❌ Never Use✅ Recommended for clients✅ Recommended for Admins

Complete diagnostics script (diagnose-ftp.sh)#

This utility automates checks on the operational status of your file transfer services.

#!/bin/bash
# diagnose-ftp.sh - FTP/FTPS/SFTP services diagnostics

set -euo pipefail

echo "=== FILE TRANSFER SERVICES DIAGNOSTICS ==="
echo ""

# 1. Verify running daemons
echo "[1/5] Verifying daemon status:"
if systemctl is-active --quiet pure-ftpd 2>/dev/null; then
    echo "  ✅ Pure-FTPd is ACTIVE."
elif systemctl is-active --quiet proftpd 2>/dev/null; then
    echo "  ✅ ProFTPD is ACTIVE."
else
    echo "  ❌ No standard FTP daemon is running."
fi

if systemctl is-active --quiet sshd 2>/dev/null; then
    echo "  ✅ SSHD (SFTP) is ACTIVE."
else
    echo "  ❌ SSHD (SFTP) is INACTIVE."
fi

# 2. Check listening ports
echo ""
echo "[2/5] Checking listening ports (ss):"
ss -lntup | grep -E "21|990|22" || echo "  ⚠️ No active listeners on ports 21, 990, or 22."

# 3. Check TLS configurations for Pure-FTPd
echo ""
echo "[3/5] Verifying FTP TLS settings:"
if [ -f /etc/pure-ftpd/conf/TLS ]; then
    TLS_VAL=$(cat /etc/pure-ftpd/conf/TLS)
    case "$TLS_VAL" in
        0) echo "  ⚠️ TLS Disabled (Insecure!)." ;;
        1) echo "  ✅ TLS Optional (Accepts plaintext)." ;;
        2) echo "  ✅ TLS Enforced (Secure)." ;;
        *) echo "  ❓ Custom or unknown TLS value: $TLS_VAL" ;;
    esac
else
    echo "  ℹ️ /etc/pure-ftpd/conf/TLS not found."
fi

# 4. Check IP connection limits
echo ""
echo "[4/5] Checking connection limits per IP:"
if [ -f /etc/pure-ftpd/conf/MaxClientsPerIP ]; then
    echo "  Pure-FTPd MaxClientsPerIP: $(cat /etc/pure-ftpd/conf/MaxClientsPerIP)"
else
    echo "  No explicit MaxClientsPerIP limit found."
fi

# 5. Extract recent logs
echo ""
echo "[5/5] Extracting recent error logs (last 5 minutes):"
if [ -f /var/log/pure-ftpd.log ]; then
    tail -n 10 /var/log/pure-ftpd.log | grep -i "fail\|error\|deny" || echo "  No errors found in Pure-FTPd logs."
else
    journalctl -u pure-ftpd --since "5 minutes ago" 2>/dev/null | grep -i "fail\|error" || echo "  No errors found in journalctl."
fi

echo ""
echo "=== DIAGNOSTICS COMPLETED ==="

Make the script executable:

chmod +x diagnose-ftp.sh

Support team checklist (n1/n2 SLA)#

Use this operational list to quickly triage "FTP cannot connect" tickets:

Was this article helpful?

Leave a quick reaction to help prioritize future technical guides:

CC BY-NC

This post is licensed under CC BY-NC.

Comments

Join the discussion below.

0 comments