Securing your server with fail2ban and a proper SSH-configuration

When I initially set up my server for this blog and my small photography-website I didn’t really think about securing it against attacks. But one day I looked into the “secure”-logfile of my server and was greetet with this:

Apr 24 04:38:23 vps9533 sshd[21882]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=122.72.120.117  user=root
Apr 24 04:38:25 vps9533 sshd[21882]: Failed password for root from 122.72.120.117 port 29063 ssh2
Apr 24 04:38:25 vps9533 sshd[21883]: Received disconnect from 122.72.120.117: 11: Bye Bye
Apr 24 04:38:28 vps9533 sshd[21884]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=122.72.120.117  user=root
Apr 24 04:38:30 vps9533 sshd[21884]: Failed password for root from 122.72.120.117 port 29300 ssh2
Apr 24 04:38:30 vps9533 sshd[21885]: Received disconnect from 122.72.120.117: 11: Bye Bye
Apr 24 04:38:33 vps9533 sshd[21886]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=122.72.120.117  user=root
Apr 24 04:38:35 vps9533 sshd[21886]: Failed password for root from 122.72.120.117 port 29552 ssh2
Apr 24 04:38:35 vps9533 sshd[21887]: Received disconnect from 122.72.120.117: 11: Bye Bye
Apr 24 04:38:38 vps9533 sshd[21888]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=122.72.120.117  user=root
Apr 24 04:38:40 vps9533 sshd[21888]: Failed password for root from 122.72.120.117 port 29814 ssh2
Apr 24 04:38:41 vps9533 sshd[21889]: Received disconnect from 122.72.120.117: 11: Bye Bye
Apr 24 04:38:43 vps9533 sshd[21890]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=122.72.120.117  user=root
Apr 24 04:38:45 vps9533 sshd[21890]: Failed password for root from 122.72.120.117 port 30113 ssh2
Apr 24 04:38:46 vps9533 sshd[21891]: Received disconnect from 122.72.120.117: 11: Bye Bye
Apr 24 04:38:48 vps9533 sshd[21892]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=122.72.120.117  user=root

Apparently someone tried to log into my server via SSH. As I used a fairly strong password for my root-account I didn’t think too much about it. Nonetheless I wanted to do something against these kinds of attacks.

Fail2ban

fail2ban_logo
fail2ban logo

The first thing that came to my mind was to install fail2ban.

Fail2ban scans log files (e.g. /var/log/apache/error_log) and bans IPs that show the malicious signs — too many password failures, seeking for exploits, etc.

Installation

A simple “yum install fail2ban” on CentOS or RedHat is enough to install it. The source code and packages for all other Linux-distributions are also available.

Configuration

There are two main configuration files.

fail2ban.conf

In the fail2ban.conf you can define the loglevel and logtarget as well as the socket used.Set loglevel to 4 to active DEBUG-mode if you have any problems during setup.

jail.conf

Several important configuration options here. Set “ignoreip” to 127.0.0.1/8 so localhost doesn’t get banned. “Bantime” is the time in seconds a host is banned. I used 60000 seconds here. “Maxretry” ist the number of failures before a host gets banned. Set this to something more than 3 if you often mistype your password or else you get locked out.

Further down below there are the templates you should use. I use “ssh-iptables” in my setup. Set the “enabled”-option to true. If you need to enable other templates, e.g. for apache or postfix, enable them.

Since I use nginx, I also added a template for nginx. There’s a great tutorial on how to configure fail2ban with nginx here.

Finally start fail2ban. If it doesn’t start there’s something wrong with your configration.

SSH-Configuration

Since I activated fail2ban my inbox got spammed with mails from fail2banm saying there was a break-in attempt. Also the server was at 100% load. The cause of this was fail2ban. Because there were so many login-attempts fail2ban had to read very much information from the secure-log and thus effectively crashed my server. So I figured I’d have to secure my ssh-daemon, too.

sshd_config

There are several configuration options to secure your server:

1. Port

Set your port to something other than 22. This blocks almost any login-attempts since most automated attacks only aim at port 22.

2. Permit root-login

Set this to “no” so you cannot login with your root account via ssh anymore. You should always login with a non priviliged account to your machine.

3. AllowUsers

Here you should only write the name of an unpriviliged (NOT root) account. So you can only login with this account. Be sure to have a strong password for this user.

4. Advanced: PasswordAuthentication

Set this to “no” to diable login with a password. Then you can only login with a ssh-key.

There are countless tutorials on the internet on how to setup password-less ssh-login.

Conclusion

Enabling fail2ban and configuring my sshd limited the attacks to almost none.

Be sure to recheck everything twice or else you could lock out yourself from your system!

Distro Picker: Chooses your Linux-Distribution

Allan McRae, Pacman developer, found an interesting small tool, called Distro Picker. This tool finds the Linux-Distribution based on what you prefer as a Desktop Environment, Package Manager and other settings.

When I tried it out, it offered me my current distro: Arch Linux, so interestingly, it works really well!

I’ll definitely have to take a look at the other distros offered.

 

Google’s nginx plugin ngx_pagespeed tested

Today I tested the new plugin for nginx developed Google: ngx_pagespeed. It’s the equivalent for nginx to apache’s mod_pagespeed.

Installation

Just go to the github page for ngx_pagespeed and the follow the instructions there.I used the alpha-r2748-build.

Sidenote: With it I ran into a problem with the wp-admin dashboard when trying to write this post. If you’re interested, check out the issue on Github.

Test scenario

For testing I used my static-content-only photography-website with gzipped content, minified css and cached static files. Initially I thought that’s not really something worth testing with, but the test-results showed that even there I could optimize the page speed with ngx_pagespeed.

I used the test suite from http://www.webpagetest.org/ to get a first look.

Test runs

First run

The first run was without ngx_pagespeed enabled:

Document Complete Fully Loaded
Load Time First Byte Start Render Speed Index Time Requests Bytes In Time Requests Bytes In
First View 2.853s 0.864s 1.961s 2783 2.853s 12 163 KB 4.499s 12 164 KB
Repeat View 0.741s 0.284s 0.718s 1900 0.741s 2 2 KB 1.155s 3 2 KB

As you can see, loading the webpage for the first time took 2.853 seconds, subsequent loadings took 0.741 seconds.

Second run

The second run was with ngx_pagespeed enabled but without a fetcher.

Document Complete Fully Loaded
Load Time First Byte Start Render Speed Index Time Requests Bytes In Time Requests Bytes In
First View 2.341s 0.605s 1.839s 2352 2.341s 10 163 KB 4.335s 11 163 KB
Repeat View 3.711s 0.277s 1.074s 2890 3.711s 7 164 KB 3.847s 8 165 KB

As you can see, the page loaded 0.5 seconds faster than without pagespeed enabled. But strangely, the repeated views took considerably longer as if caching didn’t happen.

Third run

Third run was with pagespeed and fetcher enabled.

Document Complete Fully Loaded
Load Time First Byte Start Render Speed Index Time Requests Bytes In Time Requests Bytes In
First View 2.049s 0.321s 1.520s 1925 2.049s 11 150 KB 2.311s 12 151 KB
Repeat View 0.727s 0.274s 0.625s 1107 0.727s 3 2 KB 0.989s 4 2 KB

With the fetcher enabled the first and subsequent views were finally both fater than without pagespeed.

Result

It’s worth it for my small static-content-only webpage.

Arch Linux + Nginx + PHP-FPM + WordPress

This is my little how-to on installing and configuring WordPress on Arch Linux with nginx+php-fpm. It is by no means the best way to do it or even complete, but it helped me get my blog out in minutes.

Assuming your already installed MySQL(or MariaDB), nginx via pacman and php-fpm from the AUR.

First configure php-fpm.


[www]
# The user nginx and php-fpm does run under.
user = nginx
group = nginx

# listen on a sock-file instead of a port.
listen = /var/run/php5-fpm.sock
listen.owner = nginx
listen.group = nginx
listen.mode = 0660
listen.allowed_clients = 127.0.0.1

pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3

Then edit the php.ini to enable the MySQL-plugins:

extension=mysqli.so
extension=mysql.so

Now the important part, the nginx-configuration.

user  nginx;
worker_processes  1;
error_log  /var/log/nginx/error.log;

events {
    worker_connections  1024;
}

proxy_buffers 16 16k;
proxy_buffer_size 32k;

Now the custom WordPress-configuration:

server {
        listen          80;
        root            /var/www/html/blog;
        server_name     zufallsheld.de;
        index           index.php;

        # Use gzip compression
        # gzip_static on; # Uncomment if you compiled Nginx using --with-http_gzip_static_module
        gzip on;
        gzip_disable "msie6";
        gzip_vary on;
        gzip_proxied any;
        gzip_comp_level 5;
        gzip_buffers 16 8k;
        gzip_http_version 1.0;
        gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript image/png image/gif image/jpeg;

         # Cache static files for as long as possible
        location ~* \.(xml|ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|css|rss|atom|js|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf)$ {
        try_files $uri =404;
        expires max;
        access_log off;
        }

         # Don't cache uris containing the following segments
        if ($request_uri ~* "(\/wp-admin\/|\/xmlrpc.php|\/wp-(app|cron|login|register|mail)\.php|wp-.*\.php|index\.php|wp\-comments\-popup\.php|wp\-links\-opml\.php|wp\-locations\.php)") {
        set $cache_uri "no cache";
        }

        # Don't use the cache for logged in users or recent commenters
        if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp\-postpass|wordpress_logged_in") {
        set $cache_uri 'no cache';
        }

        # Use cached or actual file if they exists, otherwise pass request to WordPress
        location / {
        try_files /wp-content/w3tc/pgcache/$cache_uri/_index.html $uri $uri/ /index.php?q=$uri&$args;
        }

        # Rewrite minified CSS and JS files
                location ~* \.(css|js) {
                if (!-f $request_filename) {
                rewrite ^/wp-content/w3tc/min/(.+\.(css|js))$ /wp-content/w3tc/min/index.php?file=$1 last;
                }
        }

        # Add trailing slash to */wp-admin requests.
        rewrite         /wp-admin$ $scheme://$host$uri/ permanent;
         # Don't cache uris containing the following segments
        if ($request_uri ~* "(\/wp-admin\/|\/xmlrpc.php|\/wp-(app|cron|login|register|mail)\.php|wp-.*\.php|index\.php|wp\-comments\-popup\.php|wp\-links\-opml\.php|wp\-locations\.php)") {
        set $cache_uri "no cache";
        }

        location ~ \..*/.*\.php$ {
                return 404;
        }

        # normal php-configuration
        location ~* ^/(.*\.php)$ {
                #logging
                access_log /var/log/nginx/blog.access.log;
                error_log /var/log/nginx/blog.error.log notice;

                try_files $uri $uri/ index.php;
                include /etc/nginx/fastcgi_params;
                fastcgi_index index.php;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                # connect to php-fpm via socket-file
                fastcgi_pass  unix:/var/run/php5-fpm.sock;
        }

        # Deny all attempts to access hidden files such as .htaccess, .htpasswd, .DS_Store (Mac).
        # Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban)
        location ~ /\. {
                deny all;
        }

        # Deny access to any files with a .php extension in the uploads directory
        # Works in sub-directory installs and also in multisite network
        # Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban)
        location ~* /(?:uploads|files)/.*\.php$ {
                deny all;
        }

}

And that’s basically it. Now just open the browser and point it to http://yourdomain/wp-admin/install.php and configure WordPress to connect to your mysql-instance.

Helpful resources:
https://wiki.archlinux.org/index.php/MySQL
https://wiki.archlinux.org/index.php/Nginx#Configuring
http://codex.wordpress.org/Nginx (Pay attention to the permalinks-part to get pretty SEO-friendly URLs.
Elivz nginx-cxonfiguration on github