FTP servers often go down, but you can make them highly available by setting up active-passive failover.

Here’s how it works: you have two FTP servers, one active and one passive. The active server handles all FTP traffic. If the active server fails, the passive server takes over and becomes the active server. This ensures that your FTP service remains available even if one server goes down.

Let’s walk through a typical setup. We’ll use two Linux servers, ftp-primary and ftp-secondary, both running vsftpd.

Server 1: ftp-primary (Active)

  1. Install vsftpd:

    sudo apt update && sudo apt install vsftpd
    
  2. Configure vsftpd: Edit /etc/vsftpd.conf:

    listen=NO
    listen_ipv6=YES
    anonymous_enable=NO
    local_enable=YES
    write_enable=YES
    local_umask=022
    dirmessage_enable=YES
    use_localtime=YES
    xferlog_enable=YES
    connect_from_port_20=YES
    xferlog_file=/var/log/vsftpd.log
    xferlog_std_format=YES
    chroot_local_user=YES
    allow_writeable_chroot=YES
    pam_service_name=vsftpd
    rsa_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
    rsa_private_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
    ssl_enable=YES
    allow_anon_ssl=NO
    force_local_data_ssl=YES
    force_local_logins_ssl=YES
    ssl_tlsv1=YES
    ssl_sslv2=NO
    ssl_sslv3=NO
    require_ssl_reuse=NO
    ssl_ciphers=HIGH
    
    • listen=NO and listen_ipv6=YES are important for active/passive setups as we’ll use xinetd or systemd to manage the service.
    • chroot_local_user=YES and allow_writeable_chroot=YES are for security, confining users to their home directories.
    • ssl_enable=YES and related directives enforce secure FTP connections.
  3. Restart vsftpd:

    sudo systemctl restart vsftpd
    

Server 2: ftp-secondary (Passive)

  1. Install vsftpd:

    sudo apt update && sudo apt install vsftpd
    
  2. Configure vsftpd: Use the exact same /etc/vsftpd.conf as on ftp-primary. Consistency is key.

  3. Restart vsftpd:

    sudo systemctl restart vsftpd
    

Heartbeat and Failover Mechanism

We’ll use heartbeat (part of the pacemaker suite) to monitor the active server and trigger failover.

  1. Install pacemaker and corosync on both servers:

    sudo apt update && sudo apt install pacemaker corosync
    
  2. Configure corosync: On ftp-primary, edit /etc/corosync/corosync.conf:

    totem {
      version: 2
      cluster_name: my_ftp_cluster
      transport: knet
      interface {
        ringnumber: 0
        bindnetaddr: 192.168.1.0 # Replace with your network subnet
      }
      secauth: on
      crypto_cipher: aes256
      crypto_hash: sha256
    }
    
    logging {
      fileline: off
      to_syslog: yes
    }
    
    nodelist {
      node {
        ring0_addr: 192.168.1.100 # IP of ftp-primary
        nodeid: 1
      }
      node {
        ring0_addr: 192.168.1.101 # IP of ftp-secondary
        nodeid: 2
      }
    }
    

    Copy this file to ftp-secondary and ensure the ring0_addr values are correct for each server.

  3. Start corosync: On both servers:

    sudo systemctl start corosync
    sudo systemctl enable corosync
    
  4. Configure pacemaker: On ftp-primary (this is where you define the cluster resources):

    # Create a virtual IP address that will float between servers
    sudo pcs resource create VirtualIP ocf:heartbeat:IPaddr2 ip=192.168.1.200 cidr_netmask=24 op monitor interval=30s
    
    # Create a resource for vsftpd
    sudo pcs resource create FTP_Service ocf:heartbeat:LVM-Thin-Monitor op monitor interval=30s --clone
    
    # Create a resource that ensures vsftpd is running on the active node
    sudo pcs resource create FTP_Service ocf:heartbeat:Systemd systemd=vsftpd op monitor interval=30s
    
    # Define the failover order: VIP first, then FTP service
    sudo pcs constraint order promote VirtualIP then FTP_Service symmetrical=false
    
    # Define that FTP_Service should run on the same node as VirtualIP
    sudo pcs constraint colocation add FTP_Service with VirtualIP INFINITY
    
    # Set the cluster to start
    sudo pcs cluster start --all
    
    • VirtualIP: This is a floating IP address. When the active server is running, this IP will be assigned to it. Clients connect to this IP. If the server fails, pacemaker will move this IP to the passive server.
    • FTP_Service: This Systemd resource tells pacemaker to manage the vsftpd service. The --clone flag is used here incorrectly in the example and should be removed if you intend to use colocation and order constraints for active/passive. For a true active-passive, you’d typically use ms (master/slave) resource types or a simple clone with no colocation if you wanted both to run but only one to be active via a floating IP. Let’s correct this for active-passive:

    Corrected pacemaker configuration for Active-Passive:

    On ftp-primary:

    # Create a virtual IP address that will float between servers
    sudo pcs resource create VirtualIP ocf:heartbeat:IPaddr2 ip=192.168.1.200 cidr_netmask=24 op monitor interval=30s
    
    # Create an Fencing resource (essential for production) - Placeholder for illustration
    # sudo pcs stonith create fence_node fence_node ipaddr=192.168.1.100 pcmk_host_list=ftp-primary pcmk_reboot_timeout=60
    
    # Create the vsftpd service as a master/slave resource
    # This makes one server the master (active) and the other the slave (passive)
    sudo pcs resource create FTP_Service ocf:heartbeat:Systemd systemd=vsftpd op monitor interval=30s meta master-max=1 master-node-max=1 clone-max=2 clone-node-max=1
    
    # Define the failover order: VirtualIP should be started before FTP_Service
    sudo pcs constraint order promote VirtualIP then FTP_Service symmetrical=false
    
    # Define that FTP_Service should run on the same node as VirtualIP
    sudo pcs constraint colocation add FTP_Service with VirtualIP INFINITY
    
    # Enable the cluster
    sudo pcs cluster enable --all
    
    • FTP_Service as master-max=1 and clone-max=2 sets it up as a master/slave resource, meaning only one instance will be active at a time.
    • stonith (Shoot The Other Node In The Head) is crucial for production environments to ensure a failed node is truly down before the passive node takes over. This example omits the complex stonith setup for brevity but is vital for preventing split-brain scenarios.
  5. Verify Cluster Status: On either server:

    sudo pcs status
    

    You should see VirtualIP and FTP_Service running on one of the nodes (the active one).

How Failover Works

corosync monitors the cluster nodes. If ftp-primary stops responding (e.g., network failure, crash), corosync on ftp-secondary detects this. pacemaker then:

  1. Stops VirtualIP on ftp-primary.
  2. Starts VirtualIP on ftp-secondary.
  3. Promotes FTP_Service to master on ftp-secondary (starting vsftpd).

Clients connecting to 192.168.1.200 will automatically reconnect to ftp-secondary once it becomes active.

Important Considerations:

  • Shared Storage: For chroot environments and user data, you’ll need shared storage (like NFS or SAN) that both servers can access. This ensures user home directories and files are available regardless of which server is active.
  • Firewall: Ensure your firewalls allow traffic on the FTP control port (21) and data ports (often ephemeral in passive mode, or ports 20 and a range if configured).
  • Fencing (STONITH): In a real-world scenario, you must configure a STONITH device. This prevents two nodes from thinking they are both active simultaneously (split-brain), which can lead to data corruption.

The next step is to configure your clients to use the floating IP address 192.168.1.200 for all FTP connections.

Want structured learning?

Take the full Ftp course →