{"id":500,"date":"2020-10-17T17:06:01","date_gmt":"2020-10-17T17:06:01","guid":{"rendered":"https:\/\/dft.wiki\/?p=500"},"modified":"2026-06-09T13:32:42","modified_gmt":"2026-06-09T17:32:42","slug":"vsftpd-tls-on-ubuntu-20-04","status":"publish","type":"post","link":"https:\/\/dft.wiki\/?p=500","title":{"rendered":"VSFTPD + TLS + Fail2Ban on Ubuntu"},"content":{"rendered":"<p>Assuming you already have an SSL\/TLS key that you may have created for your HTTP server [<a href=\"https:\/\/dft.wiki\/?p=233\">Read It<\/a>], we will use it for VSFTPD.<\/p>\n<pre>sudo apt update\r\nsudo apt install vsftpd\r\nsudo service vsftpd status\r\nsudo ufw allow from <strong>200.200.200.200<\/strong> to any<\/pre>\n<p>For now, allow your public IP (replace <strong>200.200.200.200<\/strong> with yours) to have full access to your server to avoid any issues with open ports on your firewall. We will remove this rule later and apply more precise port settings.<\/p>\n<p>Test connecting to your FTP server with FileZilla (for example). Now edit the configuration file:<\/p>\n<pre>sudo cp \/etc\/vsftpd.{conf,bkp}\r\nsudo nano \/etc\/vsftpd.conf<\/pre>\n<p>Find all of these settings and make them match the following:<\/p>\n<pre>listen=NO\r\nlisten_ipv6=YES\r\nanonymous_enable=NO\r\nlocal_enable=YES\r\nwrite_enable=YES\r\nlocal_umask=022\r\ndirmessage_enable=YES\r\nuse_localtime=YES\r\nxferlog_enable=YES\r\nconnect_from_port_20=YES\r\nsecure_chroot_dir=\/var\/run\/vsftpd\/empty\r\npam_service_name=vsftpd\r\nutf8_filesystem=YES<\/pre>\n<p>For SSL\/TLS, add the following at the end of the file:<\/p>\n<pre>rsa_cert_file=\/etc\/apache2\/md\/domains\/<strong>domain.com<\/strong>\/pubcert.pem\r\nrsa_private_key_file=\/etc\/apache2\/md\/domains\/<strong>domain.com<\/strong>\/privkey.pem\r\nssl_enable=YES\r\nallow_anon_ssl=NO\r\nforce_local_data_ssl=YES\r\nforce_local_logins_ssl=YES\r\nssl_tlsv1=YES\r\nssl_sslv2=NO\r\nssl_sslv3=NO\r\nrequire_ssl_reuse=NO\r\nssl_ciphers=HIGH<\/pre>\n<p><strong>Note:<\/strong> This tutorial assumes the server already has a certificate issued by Let&#8217;s Encrypt using Apache&#8217;s MD module. If your certificate was generated differently, locate those files on your system. In most cases, only the domain name in the paths above will need to change.<\/p>\n<p>You should also enable Passive Mode (recommended):<\/p>\n<pre>pasv_enable=Yes\r\npasv_min_port=40000\r\npasv_max_port=50000<\/pre>\n<p>Restart the service, confirm it is running, and test the connection from FileZilla:<\/p>\n<pre>sudo systemctl restart vsftpd\r\nsudo systemctl status vsftpd<\/pre>\n<p>Now remove the broad firewall rule and replace it with precise ones. First, find the rule number:<\/p>\n<pre>sudo ufw status numbered | grep <strong>200.200.200.200<\/strong><\/pre>\n<p>Remember that <strong>200.200.200.200<\/strong> is your client&#8217;s public IP, not the server&#8217;s IP.<\/p>\n<pre><strong>[ 7]<\/strong> Anywhere    ALLOW IN    200.200.200.200<\/pre>\n<p>The number in brackets is the rule to delete. In this example it is 7. Delete it (confirm when prompted):<\/p>\n<pre>sudo ufw delete <strong>7<\/strong><\/pre>\n<p>Add the new rules:<\/p>\n<pre>sudo ufw allow 20:21\/tcp comment \"FTP\"\r\nsudo ufw allow <strong>40000:50000<\/strong>\/tcp comment \"FTP Passive Mode\"<\/pre>\n<p>The second rule applies only if you enabled Passive Mode. The port range (<strong>40000:50000<\/strong>) must match exactly between the VSFTPD configuration file and the firewall.<\/p>\n<p>How wide should the range be? That is up to you. A smaller range like 50 ports is fine if you do not expect many simultaneous transfers.<\/p>\n<p>Now configure Fail2Ban to monitor and protect the FTP server. Assuming you already have Fail2Ban running [<a href=\"https:\/\/dft.wiki\/?p=401\">Read It<\/a>], you just need to add VSFTPD to the configuration:<\/p>\n<pre>sudo nano \/etc\/fail2ban\/jail.local<\/pre>\n<p>Search for <strong>vsftpd<\/strong> (Ctrl+W) and make sure it looks like this:<\/p>\n<pre>[<strong>vsftpd<\/strong>]\r\nenabled = true\r\nport = ftp,ftp-data,ftps,ftps-data\r\nlogpath = %(vsftpd_log)s<\/pre>\n<p>Then open the filter file:<\/p>\n<pre>sudo nano \/etc\/fail2ban\/filter.d\/vsftpd.conf<\/pre>\n<p>It should look like the following. If it differs, replace its contents with this:<\/p>\n<pre>[INCLUDES]\r\nbefore = common.conf\r\n[Definition]\r\n__pam_re=\\(?%(__pam_auth)s(?:\\(\\S+\\))?\\)?:?\r\n_daemon = vsftpd\r\nfailregex = ^%(__prefix_line)s%(__pam_re)s\\s+authentication failure; logname=\\S* uid=\\S* euid=\\S* tty=(ftp)? ruser=\\S* rhost=&lt;HOST&gt;(?:\\s+user=.*)?\\s*$\r\n^ \\[pid \\d+\\] \\[[^\\]]+\\] FAIL LOGIN: Client \"&lt;HOST&gt;\"(?:\\s*$|,)\r\n^ \\[pid \\d+\\] \\[root\\] FAIL LOGIN: Client \"&lt;HOST&gt;\"(?:\\s*$|,)\r\nignoreregex =<\/pre>\n<p>Restart Fail2Ban, check the status of the new jail, and attempt a failed login to confirm it is being captured:<\/p>\n<pre>sudo systemctl restart fail2ban\r\nsudo fail2ban-client status vsftpd<\/pre>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-509 size-full\" src=\"https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2020\/10\/Screenshot-from-2020-10-17-13-18-32.png\" alt=\"\" width=\"246\" height=\"160\" \/><\/p>\n<p>After a failed login attempt, &#8220;<strong>Total failed<\/strong>&#8221; will start counting.<\/p>\n<p>If you accidentally ban yourself, run the following command with <strong>your IP address<\/strong>:<\/p>\n<pre>sudo fail2ban-client unban <strong>200.200.200.200<\/strong><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Assuming you already have an SSL\/TLS key that you may have created for your HTTP [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[],"class_list":["post-500","post","type-post","status-publish","format-standard","hentry","category-linux"],"_links":{"self":[{"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/posts\/500","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dft.wiki\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=500"}],"version-history":[{"count":12,"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/posts\/500\/revisions"}],"predecessor-version":[{"id":5818,"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/posts\/500\/revisions\/5818"}],"wp:attachment":[{"href":"https:\/\/dft.wiki\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=500"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dft.wiki\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=500"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dft.wiki\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=500"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}