GENTOO SERVER SETUP
Set Up your own Gentoo based Web Server
***
Introduction
First of all let me state:
-I'm not insane. ¯\\_(ツ)_/¯
-It is really worth the effort.
Contrary to popular beliefs Gentoo is not a time consuming left in the past fringe distro. Trough the last 8 years I've used and tested most of existing Linux and *BSD based Distributions - that is a fascinating yet tedious hobby of mine. Don't ask me why.. - it is just my thing. By most I mean, like distrowatch.com from top to bottom (and a few more - non existing any more).
Currently I have a few Gentoo based servers with a quite decent uptime (not like it is a bad thing..) - and I can bet that they are working a way better than any comparable Ubuntu, CentOS, etc.. server I've used in the past.
I know - this is a bold statement to say things like that. But let me explain:
1. Continous uptime without the need of reboot to switch to the new version of the kernel, services,etc.. (sometimes it is tricky but yet possible (kernel - duh!)).
2. System is fully customisable, every single part can be adjusted as you like it, every daemon, service,.. - you decide what you want - not the distro maintainers with all those tedious dependencies forcing you to use THAT version of THAT software with tons of bloat as a dependecies. You say 'every Linux is customisable' - and I laught :D .
For starters simple example: NTP daemon (`net-misc/ntp`) is quite resource heavy for a small silly daemon - I've changed it to openntpd (NTP server ported from OpenBSD - it use less resources).
Same thing is with all log damemons, web servers, firewalls, etc.. Yes, you can do things like that on any other distro. But I wonder how much od your's precious time will you waste to do just that? :D
3. Low resource footprint (for real - it is lower than anything else I have ever used and seen in the past - lower than debian - for real). Results are almost similar to LFS if system is set correctly.
***
Hosting
You can install it on your own hardware if you have it. You can rent a rack somewhere if you have cash to burn. Or you can use VPS. There are a few VPS providers out there allowing you to set up your own Gentoo VPS directly (Shout out to Linode) or to install your own ISO (Awesome people at Gandi.net). I'm aware of the fact that Amazon AWS have Gentoo images, but I haven't used them and I can't say anything about it (folks at Dowd and Associates are responsible for those system images).
-I'm sure there are other providers - I'm not aware of - offering custom ISO install allowing you to set your own Gentoo on their infrastructure.
-If you are lucky to have your own server (not the cloud thingy) I'm sure you can use this howto without any problems or changes.
*I'm going to use Linode's VPS as an installation example here. Adapt it to your own needs as you like.*
I won't cover here installation process - Linode will roll it out for you automatically with the help of their installation scripts - just do some magic with with help of your mouse and keyboard. It is straightforward process and it is extremely easy. Otherwise install it on your own with the use of one of the best documentations written ever (just right after *BSD documentation) at the Gentoo.org website.
****
First Steps
*I assume that you already installed your Gentoo based system and you know how to connect through SSH onto it. Your server should be up and running already of course.*
Lets start and log in to our new server through the SSH. Enter the following into your terminal window or application (putty --> if you are using silly Windows based OS :/ ). Be sure to replace the example IP address with your server IP address (Linode users: you can find it in the → 'Linodes' tab → 'Remote Access' tab). As a example here I'll use IP: 123.456.78.9 address:
Yes, unfortunately first login is as a root user. We'll change it soon during the initial configuration of our new server.
But first things first let's synchronize server repositories:
emerge --sync
I would recommend update the whole thing now (at Gentoo it is better to know about any problems at the begining):
emerge -uavDN system && emerge -uavDN world
I hope you came here with some knowledge already, and there is no need to explain what this command does! If not - stop now and go read some manuals first. Gentoo won't forgive you any lack of knowledge ;)
When the update and all post-update requests are done... (joke!)
OK. First major update will for sure flag some problems with packages dependecies. Dealing with those errors is sometimes tricky and time consuming. But in Gentoo there is no way you cannot work around it. Gentoo documentation is very detailed and you will love it. Most of the problems are easy to avoid if you follow portage documentation.
You will be forced to update content of portage configuration files in the `/etc/portage/`like: setting appropriate flags for packages in the `/etc/portage/package.use`, configuring entire system in the `/etc/portage/make.conf `, setting package versions in the `/etc/portage/package.mask`, `/etc/portage/package.unmask` and `/etc/portage/package.accept_keywords`.
Just make sure you know what you are doing there!
After that carefully read what `etc-update` and `dispatch-conf` tools are telling you to do and follow instructions.
And finally when the `emerge -uavDN system && emerge -uavDN world` update is done fully and Gentoo accepted your sacrifice :D - you can begin setting up and configure your server for web.
Let's start with setting up a Hostname and fully qualified domain name (FQDN) and enter the following commands to set the hostname, replacing hostname with the hostname of your choice:
echo "HOSTNAME=\"hostname\"" > /etc/conf.d/hostname
and then:
/etc/init.d/hostname restart
Hostname can be set also in the:
echo "your hostname" > /etc/hostname
You should check which configuration your server has. just check inside of /etc directory for hostname file or inside /etc/conf.d/. Then pick the right command. Not before!
After that check your hostname:
hostname -F /etc/hostname
hostname
Next lets update the /etc/hosts
file.
This file creates static associations between IP addresses and hostnames, with higher priority than DNS!
In the example below, 123.456.78.9
is our public accessible IP address, hostname
is our local hostname, and hostname.example.com
is our FQDN. Your /etc/hosts
file should look like that:
127.0.0.1 localhost.localdomain localhost
123.456.78.9 hostname.example.com hostname
Don't edit first '127.0.0.1' line, it is for localhost internal server loopback network - but it is highly recommended to add the second line with the right IP address and your Fully Qualified Domain Name.
You are the owner of example.com
and in the /etc/hostname
your server has its own name (here it is hostname
but you can name it as you want it - it doesn't really matter). FQDN does not need to have any relationship to websites hosted on this server. As an example, you might host “example.com” website on your server, but the system’s FQDN might be “xyz.example.com.”
It is highly recommended to have registered domain/subdomain on the DNS level for FQDN purposes.
hostname.example.com
is our FQDN here. The value you assign as your system’s FQDN should have an “A” record in DNS pointing to your's server address.
As you can see here all server configuration files are self explanatory :D
Lets continue now with setting up the Timezone for the server.
View the list of all available zone files:
ls /usr/share/zoneinfo
And manually symlink a zone file in /usr/share/zoneinfo to /etc/localtime:
ln -sf /usr/share/zoneinfo/Europe/London /etc/localtime
As I'm based in the UK I'm going to set it up in this example for the UK based zone.
Confirm that the timezone is set correctly:
date
Add a Limited User Account for day-to-day use with limited rights (in this example I choose a 'larry' username):
useradd -m -G users,wheel -s /bin/bash larry
and set up a password:
passwd larry
Add user to the wheel
group (wheel
sudo group so you’ll have administrative privileges):
usermod -aG wheel larry
Check if the wheel
group is uncommented in the /etc/sudoers
file:
nano /etc/sudoers
and look for the line:
%wheel ALL=(ALL) ALL
Save all changes.
With the new user set you can now log out from your VPS. There is no need to use a root account to log into your server (it is considered as a extremely stupid and dangerous though - we'll block that option later).
exit
Log back in with your new user (here it will be: 'Larry'). Remember to use IP address of your server:
Now you can administer server from your new user account instead of root. If you really want to become a root
user just use the:
su -
Make neccessary changes and then exit
to become a normal user again.
You can also use sudo
command instead of switching between users:
sudo example-command
Use your everyday user with the 'sudo' priviliges for all mayor administration commands.
***
**Harden SSH Access:**
By default, password authentication is used to connect to most VPS via SSH (at least at Linode, Nephoscale, AWS, Gandi). And we are going to change that with the use of the SSH keys. A cryptographic key pair is more secure way to log in to your VPS because a private key takes the place of a password, which is generally much more difficult to brute-force. Why?
SSH keys serve as a means of identifying yourself to an SSH server using public-key cryptography and challenge-response authentication. One immediate advantage this method has over traditional password authentication is that you can be authenticated by the server without ever having to send your password over the network. Anyone eavesdropping on your connection will not be able to intercept and crack your password because it is never actually transmitted. Additionally, as I wrote previously using SSH keys for authentication virtually eliminates the risk posed by brute-force password attacks by drastically reducing the chances of the attacker correctly guessing the proper credentials.
I'll use here default RSA based key therefore there is no need to specify `ssh-keygen` with the `-t` option. RSA provides the best compatibility of all algorithms but requires the key size to be larger to provide sufficient security.
Minimum key size is 1024 bits, default is 2048 (see `man ssh-keygen`) and maximum is 16384.
Create an Authentication Key-pair:
**(This has to be done on your local computer, not on the VPS !!!)**
We are going to create a 8192-bit RSA key pair here. There are voices in the community that we should use bigger, better, stronger keys. But key is only one small part of the security. And right now, a default 2048-bit RSA key, or any greater length (such as the 4096-bit key size of the Github suggestion), is unbreakable with today's technology and known factorization algorithms. Even with very optimistic assumptions on the future improvements of computational power available for a given price (namely, that Moore's law holds at its peak rate for the next three decades), a 3072-bit RSA key won't become breakable within the next 30 years by Mankind as a whole, let alone by an Earth-based organization. Of course, there always remains the possibility of some unforeseen mathematical breakthrough that makes breaking RSA keys a lot easier. Unforeseen breakthroughs are, by definition, unpredictable, so any debate on that subject is by nature highly speculative.
If you need more security than default RSA-2048 offers, the way to go would be to switch to elliptical curve cryptography (ed25519)
During creation of the key, you will be given the option to encrypt the private key with a passphrase. This means that key cannot be used without entering the passphrase. I suggest to use the key pair with a passphrase.
ssh-keygen -b 8192
Press Enter
to use the default names id_rsa and id_rsa.pub in /home/your_username/.ssh - before entering your passphrase.
Next let's upload the public key to your server. Replace larry
user with the name of the user you created on the server, and 172.16.254.1 with your VPS IP address.
From your local computer:
ssh-copy-id [email protected]
Or if you prefer scp
command:
On the VPS create .ssh
directory and change permissions for it:
mkdir -p ~/.ssh && sudo chmod -R 700 ~/.ssh/
From your local computer:
scp ~/.ssh/id_rsa.pub [email protected]:~/.ssh/authorized_keys
For the security reasons I would strongly advise to disallow root
logins over SSH. All SSH connections will be made by non-root user (larry in this example). Once a limited user account (larry) is connected, administrative privileges are accessible either by using sudo
or changing to a root shell using su -
command.
Lets edit a /etc/ssh/sshd_config
file:
nano /etc/ssh/sshd_config
and change yes
to no
in the PermitRootLogin
line:
# Authentication:
...
PermitRootLogin no
As we are going to use only key based authentication I recommend to disable SSH password authentication. This will require for all users allowed to connect via SSH to use key authentication only. Edit the same file:
nano /etc/ssh/sshd_config
change the line PasswordAuthentication yes
to disable clear text passwords:
PasswordAuthentication no
!!!Though you may want to leave password authentication enabled if you connect to your Linode server from many different computers. This will allow you to authenticate with a password instead of generating and uploading a key-pair for every device.
Continue with another flags for ssh in the /etc/ssh/sshd_config
file.
Specify to listen on only one internet protocol. The SSH daemon listens for incoming connections over both IPv4 and IPv6 by default. Unless you need to SSH into your Linode using both protocols, disable whichever you do not need. This does not disable the protocol system-wide, it is only for the SSH daemon.
Add this option as AddressFamily
is usually not in the /etc/ssh/sshd_config
file by default. AddressFamily inet
to listen only on IPv4 or AddressFamily inet6
to listen only on IPv6.
Change accordingly to yours needs:
echo 'AddressFamily inet' | sudo tee -a /etc/ssh/sshd_config
or
echo 'AddressFamily inet6' | sudo tee -a /etc/ssh/sshd_config
Finally change ssh port from default 22 to something different:
# What ports, IPs and protocols we listen for
Port 3000
In this example I'm going to use port 3000 for all ssh connections.
After that restart the SSH service to load the new configuration.
sudo rc-service sshd restart
***
**Fail2ban for SSH**
Fail2ban is a log-parsing application that monitors system logs for symptoms of an automated attack on your server. When an attempted attack is discovered, Fail2ban will add a new rule to iptables, thus blocking the IP address of the attacker (for a set amount of time or permanently - up to you really).
Install Fail2ban and iptables:
emerge -av net-analyzer/fail2ban net-firewall/iptables net-firewall/ipset
Iptables should be already installed - it is only to make sure that we have it in the system. We'll configure it a bit later.
For now go to /etc/fail2ban
. Within this directory are all Fail2ban configuration files:
cd /etc/fail2ban
and check configuration files there:
ls -l
The default fail2ban configuration file is location at /etc/fail2ban/jail.conf. The configuration work should not be done in that file, however, and we will instead make a local copy of it.
cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
After the file is copied, you can make all of your changes within the new jail.local file
. Many of possible services that may need protection are in the file already. Each is located in its own section, configured and turned off by default.
Edit jail.local
file:
nano /etc/fail2ban/jail.local
The first section of defaults covers the basic rules that fail2ban will follow. If you want to set up more nuanced protection for your virtual private server, you can customize the details in each section.
Write your personal IP address into the ignoreip
line. You can separate each address with a space. IgnoreIP allows you white list certain IP addresses and make sure that they are not locked out from your server. Including your address will guarantee that you do not accidentally ban yourself.
[DEFAULT]
# "ignoreip" can be an IP address, a CIDR mask or a DNS host. Fail2ban will not
# ban a host which matches an address in this list. Several addresses can be
# defined using space separator.
ignoreip = 127.0.0.1
# "bantime" is the number of seconds that a host is banned.
bantime = 3600
# A host is banned if it has generated "maxretry" during the last "findtime"
# seconds.
findtime = 600
# "maxretry" is the number of failures before a host get banned.
maxretry = 3
Bantime
parameter is the number of seconds that a host would be blocked from the server if they are found to be in violation of any of the rules. This is especially useful in the case of various bots, that once banned, will simply move on to the next target. The default is set for 10 minutes—you may raise this to an hour (3600sec or even higher).
Maxretry
parameter is the amount of incorrect login attempts that a host may have before they get banned for the length of the ban time.
Findtime
parameter refers to the amount of time that a host has to log in. The default setting is 10 minutes; this means that if a host attempts, and fails, to log in more than the maxretry number of times in the designated 10 minutes, they will be banned. Sweet - isn't it? :)
Now lets check and configure the ssh-iptables section in the /etc/fail2ban/jail.local
file.
The SSH details section is by default already set up and turned on. Although you should not be required to make any changes within this section, you can find the details:
[ssh-iptables]
enabled = true
filter = sshd
action = iptables[name=SSH, port=3000, protocol=tcp]
logpath = /var/log/secure
maxretry = 3
enabled - means that SSH protection is on. To turn it off use word: "false".
filter - is set by default to sshd, and it refers to the config file containing the rules that fail2banuses to find matches.
action - describes the steps that fail2ban will take to ban a matching IP address.
In the "iptables" parameter's details, you can customize fail2ban a bit further. As we are going to use a non-standard port for ssh connection, we need to set the port number within the brackets to match.
You can change the protocol from TCP to UDP in this line as well, depending on which one you want fail2ban to monitor.
log path - this parameter refers to the log location that fail2ban will use.
max retry - I'm sure I don't need to explain that one- right?
When all changes in the fail2ban configurations are set and saved, there is only one thing left to do. Restart the fail2ban service to catch up with all changes:
sudo rc-service fail2ban restart
***
IPTABLES:
It is time for the iptables configuration.
There are few things to do, before we install it. First go and edit /etc/portage/package.use
:
www-servers/nginx aio http http2 http-cache ipv6 nginx_modules_http_autoindex nginx_modules_http_browser nginx_modules_http_empty_gif nginx_modules_http_fancyindex nginx_modules_http_gzip ssl http_v2_module ngx_http_v2_module nginx_modules_image_filter ngx_http_empty_gif nginx_modules_http_referer nginx_modules_http_geo nginx_modules_http_geoip nginx_modules_http_gunzip nginx_modules_http2 nginx_modules_http_addition brotli
# brotli flag though won't work as it happened with spdy - those things are now default.
# required by media-gfx/graphviz-2.26.3-r4::gentoo
# required by @preserved-rebuild (argument)
>=media-libs/gd-2.0.35-r4 truetype fontconfig
# If it doesn't work change to temporary fix (old version doesn't have hpn support): -hpn
# But current openssh works just fine.
net-misc/openssh hpn
# iptables geoip:
net-firewall/iptables extensions
# ipset for non modular kernel:
net-firewall/ipset -modules
# required by www-servers/nginx-1.10.1::gentoo[nginx_modules_http_image_filter]
# required by @selected
# required by @world (argument)
>=media-libs/gd-2.2.3 png jpeg
Lets install it:
emerge -av net-firewall/iptables net-firewall/ipset
To check the rules that iptables puts in effect religiously memorise the following command:
iptables -L
Iptables is the controller for netfilter, the Linux kernel’s packet filtering framework. Iptables is included in most Linux distributions by default but is considered an advanced method of firewall control.
Appropriate firewall rules depend on the services being run. Below are iptables rulesets to secure your web server.
Setup firewall rules in a new file:
sudo nano /etc/iptables.firewall.rules
The following firewall rules will allow HTTP (80)
, HTTPS (443)
, SSH (3000)
, ping
, and some other ports for testing. All other ports will be blocked.
Paste the following into /etc/iptables.firewall.rules
:
*filter
# Allow all loopback (lo0) traffic and drop all traffic to 127/8 that doesn't use lo0
-A INPUT -i lo -j ACCEPT
-A INPUT ! -i lo -d 127.0.0.0/8 -j REJECT
# Accept all established inbound connections
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# Block anything from China
# These rules are pulled from ipset's china list
# The source file is at /etc/cn.zone (which in turn is generated by a shell script at /etc/block-china.sh )
-A INPUT -p tcp -m set --match-set china src -j DROP
# Allow all outbound traffic - you can modify this to only allow certain traffic
-A OUTPUT -j ACCEPT
# Allow HTTP and HTTPS connections from anywhere (the normal ports for websites and SSL).
-A INPUT -p tcp --dport 80 -j ACCEPT
-A INPUT -p tcp --dport 443 -j ACCEPT
# Allow SSH connections
# The -dport number should be the same port number you set in sshd_config
# And the ssh port used in this example is: 3000.
#
-A INPUT -p tcp -m state --state NEW --dport 3000 -j ACCEPT
# Allow ping
-A INPUT -p icmp -m icmp --icmp-type 8 -j ACCEPT
# Fancy to install IRC maybe? (if not hash it):
-A INPUT -i eth0 -p tcp -m tcp --sport 6667 -j ACCEPT
-A OUTPUT -o eth0 -p tcp -m tcp --dport 6667 -j ACCEPT
# Log iptables denied calls
-A INPUT -m limit --limit 5/min -j LOG --log-prefix "iptables denied: " --log-level 7
# Drop all other inbound - default deny unless explicitly allowed policy
-A INPUT -j DROP
-A FORWARD -j DROP
# If you want to show to attackers/sniffers/bots that you are tired of their drama you can then reject instead of droping all other
# inbounds.
# Default deny unless explicitly allowed policy:
# -A INPUT -j REJECT
# -A FORWARD -j REJECT
COMMIT
Yep. Rule -A INPUT -p tcp -m set --match-set china src -j DROP
is a brilliant thing. Nothing personal guys.
I was tired of all that awkward traffic from china. Actually not even a single website I'm hosting is directed for that region. I'm even thinking to extend it a bit for Russia and Ukraine IP set list as well. Websites I'm hosting usually if not always are for local markets or a single country only. Nothing 'THAT BIG' for a global reach or something like that (lets be serious though - this is one small VPS). The amount of traffic generated by sniffers, bots and bad IPs from China, Russia, Ukraine and Romania is teryfying. Thousands hits daily.. :/ For a small servers bots like those, trying to get in through the ssh port:22 every 3 seconds or sniffers looking for security hole in particular software (mysql and php related usually) it is nuisance and waste of that preciuos computing resources we are paying.
Lets build those rules now.
Start with creating a following file:
nano /etc/block-china.sh
And inside of that file write the following:
# Create the ipset list
ipset -N china hash:net
# remove any old list that might exist from previous runs of this script
rm cn.zone
# Pull the latest IP set for China
wget -P . http://www.ipdeny.com/ipblocks/data/countries/cn.zone
# Add each IP address from the downloaded list into the ipset 'china'
for i in $(cat cn.zone ); do ipset -A china $i; done
# Restore iptables
/sbin/iptables-restore < /etc/iptables.firewall.rules
Make sure the rules are in place after any reboot:
nano /etc/network-iptables-rules
Inside of that file put:
#!/bin/sh
/sbin/iptables-restore < /etc/iptables.firewall.rules
Set the script permissions:
sudo chmod +x /etc/network-iptables-rules
Before we go any further there is a need to install one more thing:
emerge -av net-firewall/ipset
Coutry based rules in IPTABLES won't work without IPSET. So we need IPSET to smartly block some countries. :/
One more thing.. we need to pull the data for that ipblock list we just created:
sh /etc/block-china.sh
Restore IPTABLES rules now to make them filter all network traffic:
iptables-restore < /etc/iptables.firewall.rules
Verify that the rules were installed correctly:
sudo iptables -vL
If you are using IPv6 protocol just repeat process for ip6tables same as shown above. The only difference will be usage of ip6tables
instead of iptables
.
Save all iptables rules for now:
rc-service iptables save
I know that we have barely scratched the surface with securing this tiny fluffy server. But it is good enough for now (cough! cough!..). I will cover security topic at the end of this documentation (when all services are set, tested and running).
Lets install some basic services for hosting websites, irc and git. As we are focusing on keeping it speed and secure there is no place for databases or things like php nasty bloat.
***
Web Server:
While I am not aware of any Gentoo specific benchmarks that have been run on the various flavors of web servers, it is typically logical to assume that the more feature-enabled by default web server you install, the more resources it would use.
I spent a lot of time reading various documentations and comparing all informations carefully. I also had in the past my ups and downs with Apache, I did some trials with Lighttpd, etc.. Long story short NGINX web server is better in the 'raw number of requests per second it can serve' than Apache or Lighttpd servers no matter how well configured they are. At higher levels of concurrency, NGINX can handle fewer requests per second, but still can serve double what Lighttpd does (which is already doing nearly 4x what Apache was ever able to do).
Though Apache supports a larger toolbox of things it can do immediately after install, yet it can be something of a memory hog with all modules enabled by default. Contrary freshly installed NGINX does not eat as much memory compared to Apache (even with enabled additional modules is still faster and scale way better).
I should also mention that nginx is a also good as a reverse proxy server!
Before we go with installation of nginx package, first we should do some magic with the USE flags for Nginx.
There are two ways of handling USE flags:
-Global (via /etc/portage/make.conf file - more information here)
-Local (via /etc/portage/package.use - more here)
Nginx uses modules to enhance its features. It uses expanded USE (USE_EXPAND) flags to denote which modules should be installed:
-HTTP related modules can be enabled through the NGINX_MODULES_HTTP variable
-Mail related modules can be enabled through the NGINX_MODULES_MAIL variable
-Third party modules can be enabled through the NGINX_ADD_MODULES variable
These variables need to be set in /etc/portage/make.conf. Modules detailed descriptions can be found in these directories:
/usr/portage/profiles/desc/nginx_modules_http.desc
and
/usr/portage/profiles/desc/nginx_modules_mail.desc.
For example, to enable the on-the-fly Brotli compression module:
Edit /etc/portage/make.conf
and add:
NGINX_MODULES_HTTP="brotli"
That will overwrite the default value of NGINX_MODULES_HTTP and set it to brotli. To enable the brotli without overwriting the default NGINX_MODULES_HTTP, use local USE flag which can be specified in /etc/portage/package.use
:
www-servers/nginx NGINX_MODULES_HTTP: brotli
You definitely should check the complete list of USE flags specified for www-servers/nginx package.
Ask what flags nginx have enabled by default:
equery uses nginx
And change flags accordingly to your needs in the /etc/portage/make.conf
. For starters it should look like:
USE="aio bindist http http-cache http2 ipv6 mmx pcre poll select sse sse2 ssl threads -cpp_test -debug -google_perftools"
NGINX_MODULES_HTTP="autoindex brotli browser charset empty_gif geo gzip limit_conn limit_req map proxy referer rewrite scgi split_clients ssi upstream_hash upstream_ip_hash upstream_keepalive upstream_least_conn upstream_zone userid uwsgi geoip gunzip gzip_static image_filter realip spdy"
I'm sure that portage with throw some errors here and there.
With all USE flags set, lets install www-servers/nginx:
emerge --ask www-servers/nginx
The nginx package will install an init service script allowing you to stop, start, or restart the service.
For example to start the nginx service do:
rc-service nginx start
To restart do:
rc-service nginx restart
You get that? ;)
BUT DON'T START NGINX YET!!
There is a lot to do with nginx configuration before firing it up. Let's start with changing a few files in the /etc/nginx/
directory.
Our points of interest for now are mime.types
nginx.conf
files. There is one more configuration inside /etc/nginx/sites-available' - lets call it
example.comfile. As you can guess it is responsible of handling
example.com` domain.
The main nginx configuration is handled through the /etc/nginx/nginx.conf
file. I'm using Cloudflare DNS, GeoIP exlusions, Gzip, and I'm focusing here on delivering websites written in pure html, css, js (no php, ruby, rust, js frameworks or other weird things cool kids use nowadays). Remember it is all about performance and simplicity here. No over complication with containers, markup conversions - just html websites. Simple as that. Lets tune main nginx configuration to squize a bit more from it:
nano /etc/nginx/nginx.conf
And do some changes here:
#
user nginx nginx;
# It is common practice to run 1 worker process per core. Anything above this won't hurt your system,
# but it will leave idle processes usually just lying about.
# To figure out what number you'll need to set worker_processes to, simply take a look at the amount
# of cores you have on your setup: grep processor /proc/cpuinfo | wc -l
# In this example I'm using small one core CPU VPS:
worker_processes 1;
error_log /var/log/nginx/error_log info;
events {
# The worker_connections command tells our worker processes how many people can simultaneously
# be served by Nginx. The default value is 768; however, considering that every browser usually
# opens up at least 2 connections/server, this number can half. To check our core's limitations
# issue: ulimit -n and use the result of this command as a good starting point.
worker_connections 1024;
use epoll;
# for a worker to accept all new connections at one time:
multi_accept on;
}
http {
### Cloudflare Real IP:
# I know what people talking about some particular companies ;)
# I also know that Cloudflare is blocking TOR nodes - which is a terrible idea.
# People also are a bit worried about their FlexibleSSL which I'm not recommending (use fullSSL or Let’s Encrypt SSL).
# To be honest - their DNS package is quite nice for me. They even offer some candy there:
# CDN, statistics (not great but always something), free SSL for my small websites, and some
# sort protection agains DDOS. And it is dead easy to set up quickly. Not a Cloudflare user? You can skip this part:
set_real_ip_from 173.245.48.0/20
set_real_ip_from 103.21.244.0/22
set_real_ip_from 103.22.200.0/22
set_real_ip_from 103.31.4.0/22
set_real_ip_from 141.101.64.0/18
set_real_ip_from 108.162.192.0/18
set_real_ip_from 190.93.240.0/20
set_real_ip_from 188.114.96.0/20
set_real_ip_from 197.234.240.0/22
set_real_ip_from 198.41.128.0/17
set_real_ip_from 162.158.0.0/15
set_real_ip_from 104.16.0.0/13
set_real_ip_from 104.24.0.0/14
set_real_ip_from 172.64.0.0/13
set_real_ip_from 131.0.72.0/22
# IPV6 Cloudflare IP:
set_real_ip_from 2400:cb00::/32
set_real_ip_from 2606:4700::/32
set_real_ip_from 2803:f800::/32
set_real_ip_from 2405:b500::/32
set_real_ip_from 2405:8100::/32
set_real_ip_from 2a06:98c0::/29
set_real_ip_from 2c0f:f248::/32
#use any of the following two:
real_ip_header CF-Connecting-IP;
#real_ip_header X-Forwarded-For;
### End of Cloudflare Real IP list ###
### My little experiment with GeoIP.
# The list is long as I don't believe in global reach for my small websites I'm hosting on this particular VPS.
# Viewer's audience is targeted here for a few countries only. I'm hoping also to cut the drama with spammers,
# and others not so happy folks on the internet.
#
# GeoIP Lite (GeoLite2) databases are free IP geolocation databases comparable to, but less accurate than, MaxMind’s GeoIP2 databases.
# You will need to set up an GeoLite2 account to download the GeoLite2 databases or to query the GeoLite2 web services.
# More informations <a href="https://dev.maxmind.com/geoip/geolite2-free-geolocation-data?lang=en">here</a>.
#
geoip_city /etc/nginx/geoip/GeoLiteCity.dat;
geoip_country /usr/share/GeoIP/GeoIP.dat;
map $geoip_country_code $allowed_country {
default yes;
# Block those countries:
AD no;
AE no;
AF no;
AG no;
AI no;
AL no;
AM no;
AO no;
AP no;
AR no;
AS no;
AW no;
AX no;
AZ no;
BA no;
BB no;
BD no;
BF no;
BG no;
BH no;
BI no;
BJ no;
BL no;
BM no;
BN no;
BO no;
BQ no;
BR no;
BS no;
BT no;
BV no;
BW no;
BY no;
BZ no;
CC no;
CD no;
CF no;
CG no;
CI no;
CK no;
CL no;
CM no;
CN no;
CO no;
CR no;
CU no;
CV no;
CW no;
CX no;
CY no;
DJ no;
DK no;
DM no;
DO no;
DZ no;
EC no;
EE no;
EG no;
EH no;
ER no;
ET no;
FI no;
FJ no;
FK no;
FM no;
GA no;
GD no;
GE no;
GF no;
GG no;
GH no;
GI no;
GM no;
GN no;
GP no;
GQ no;
GR no;
GS no;
GT no;
GU no;
GW no;
GY no;
HK no;
HM no;
HN no;
HR no;
HT no;
HU no;
ID no;
IN no;
IO no;
IQ no;
IR no;
JM no;
JO no;
JP no;
KE no;
KG no;
KH no;
KI no;
KM no;
KN no;
KP no;
KR no;
KW no;
KZ no;
LA no;
LB no;
LC no;
LK no;
LR no;
LS no;
LT no;
LV no;
LY no;
MA no;
MD no;
MG no;
MK no;
ML no;
MM no;
MN no;
MQ no;
MR no;
MT no;
MU no;
MV no;
MW no;
MX no;
MY no;
MZ no;
NA no;
NC no;
NE no;
NG no;
NI no;
NP no;
NR no;
NU no;
NZ no;
OM no;
PA no;
PE no;
PF no;
PG no;
PH no;
PK no;
PM no;
PN no;
PR no;
PS no;
PT no;
PW no;
PY no;
QA no;
RE no;
RO no;
RS no;
RU no;
RW no;
SA no;
SB no;
SC no;
SD no;
SG no;
SI no;
SJ no;
SL no;
SN no;
SO no;
SR no;
SS no;
ST no;
SV no;
SY no;
SZ no;
TC no;
TD no;
TG no;
TH no;
TJ no;
TK no;
TL no;
TM no;
TN no;
TO no;
TR no;
TT no;
TV no;
TW no;
TZ no;
UA no;
UG no;
UY no;
UZ no;
VC no;
VE no;
VG no;
VI no;
VN no;
VU no;
WF no;
WS no;
YE no;
YT no;
ZA no;
ZM no;
ZW no;
}
# geo $exclusions {
# default 0;
# 10.8.0.0/24 1;
# }
#
# About those Geoip settings - I'm not sure if that thing is working at all :/
# I'll definitely look closer at this when I have a time for that.
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main
'$remote_addr - $remote_user [$time_local] '
'"$request" $status $bytes_sent '
'"$http_referer" "$http_user_agent" '
'"$gzip_ratio"';
# client_body_buffer_size handles the client buffer size.
# Most client buffers are coming from POST method form submissions.
# 128k is normally a good choice for this setting.
# A bit overkill I know..
# If you are preparing yourself for buffer overflows scenario set it up to: 1k.
# As I'm serving it through the Cloudflare networked proxy:
client_body_buffer_size 128k;
# client_max_body_size sets the max body buffer size.
# If the size in a request exceeds the configured value,
# the 413 (Request Entity Too Large) error is returned to the client.
# For reference, browsers cannot correctly display 413 errors.
# Setting size to 0 disables checking of client request body size.
# I could go for 1k here but lets try for a bit 10m:
client_max_body_size 10m;
# client_header_buffer_size handles the client header size.
# 1k is usually a sane choice for this by default.
client_header_buffer_size 1k;
# large_client_header_buffers shows the maximum number and size of
# buffers for large client headers. 4 headers with 4k buffers should be sufficient here.
large_client_header_buffers 4 4k;
# output_buffers sets the number and size of the buffers used for reading a response from a disk.
# If possible, the transmission of client data will be postponed until Nginx has at least the
# set size of bytes of data to send. The zero value disables postponing data transmission.
output_buffers 1 32k;
postpone_output 1460;
# client_header_timeout sends directives for the time a server will wait for a header body to be sent.
client_header_timeout 3m;
# client_body_timeout sends directives for the time a server will wait for a body to be sent.
client_body_timeout 3m;
# sent_timeout specifies the response timeout to the client.
# This timeout does not apply to the entire transfer but, rather,
# only between two subsequent client-read operations. Thus, if
# the client has not read any data for this amount of time, then Nginx shuts down the connection.
send_timeout 3m;
# configuration block tells Nginx to cache 1000 files for 30 seconds,
# excluding any files that haven't been accessed in 20 seconds, and only files that have 5 times or more.
open_file_cache max=1000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 5;
open_file_cache_errors off;
# cache via a particular location <-- pushed to: /sites-available/*conf:
#location ~* .(woff|eot|ttf|svg|mp4|webm|jpg|jpeg|png|gif|ico|css|js)$ {
# expires 365d;
# }
connection_pool_size 256;
request_pool_size 4k;
# disable sending the nginx version number in error pages and Server header:
server_tokens off;
# Gzip settings:
gzip on;
gzip_disable "MSIE [1-6]\.";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_min_length 256;
gzip_types application/x-javascript text/css application/javascript text/javascript text/plain text/xml application/json application/vnd.ms-fontobject application/x-font-opentype application/x-font-truetype application/x-font-ttf application/xml font/eot font/opentype font/otf image/svg+xml image/vnd.microsoft.icon image/x-icon;
gzip_proxied expired no-cache no-store private auth;
# End of gzip settings
# sendfile optimizes serving static files from the file system, like logos:
sendfile on;
# tcp_nopush optimizes the amount of data sent down the wire at once by activating
# the TCP_CORK option within the TCP stack. TCP_CORK blocks the data until the packet
# reaches the MSS, which is equal to the MTU minus the 40 or 60 bytes of the IP header.
tcp_nopush on;
# tcp_nodelay allows Nginx to make TCP send multiple buffers as individual packets:
tcp_nodelay on;
# Keep alive allows for fewer reconnections from the browser:
keepalive_timeout 75 20;
keepalive_requests 100000;
ignore_invalid_headers on;
index index.htmL;
include /etc/nginx/sites-enabled/*.conf;
}
Inside of `nginx.conf` there are a few things we need to cover now before anything else. There is a GeoIP module to install and set up. The GeoLite databases are distributed under the Creative Commons Attribution-ShareAlike 4.0 International License by http://www.maxmind.com.
emerge -av sys-process/dcron dev-libs/geoip net-misc/geoipupdate
This will install "geoiplookup" and "geoipupdate" to update the database.
As the geoiplookup database will be pretty outdated you might want to update it regularly as IP assignment changes. One way of doing that is to use a crontab. Get that database installed. First country rules:
wget -q https://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz
-O - | gunzip > /etc/nginx/geoip/GeoIP.dat.new && mv /etc/nginx/geoip/GeoIP.dat.new /etc/nginx/geoip/GeoIP.dat
Then city rules (because why not?):
mkdir /etc/nginx/geoip && cd /etc/nginx/geoip && wget -N https://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz && gunzip GeoLiteCity.dat.gz
To update it regularly create a new file in the cron.monthly:
nano /etc/cron.monthly/GeoIP
Inside of that file put that script:
#!/bin/bash
wget -q https://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz -O - | gunzip > /etc/nginx/geoip/GeoCity.dat.new && mv /usr/share/geoip/GeoCity.dat.new /usr/share/geoip/GeoCity.dat
wget -q https://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz -O - | gunzip > /etc/nginx/geoip/GeoIP.dat.new && mv /etc/nginx/geoip/GeoIP.dat.new /etc/nginx/geoip/GeoIP.dat
Now we can start our freshly installed dcron
:
/etc/init.d/dcron start
rc-update add dcron default
GeoIP is set and running.
More informations about GeoIP datasets are here.
Lets install something lightweight to rotate system logs:
emerge -av app-admin/metalog
rc-update add metalog default
It looks a bit chaotic.. - but don't worry :). A few final touches with nginx and we are done here.
In the /etc/nginx/nginx.conf
file - there is mime.types
line - right? Lets check if we have it as it should be:
nano /etc/nginx/mime.types
Inside of mime.types
we can specify exactly what types of file nginx can serve. Here is a comprehensive list of all files you can include inside mime.types
:
types {
text/html html htm shtml;
text/css css;
text/xml xml;
image/gif gif;
image/jpeg jpeg jpg;
application/javascript js;
application/atom+xml atom;
application/rss+xml rss;
text/mathml mml;
text/plain txt;
text/vnd.sun.j2me.app-descriptor jad;
text/vnd.wap.wml wml;
text/x-component htc;
image/png png;
image/tiff tif tiff;
image/vnd.wap.wbmp wbmp;
image/x-icon ico;
image/x-jng jng;
image/x-ms-bmp bmp;
image/svg+xml svg svgz;
image/webp webp;
application/font-woff woff;
application/java-archive jar war ear;
application/json json;
application/mac-binhex40 hqx;
application/msword doc;
application/pdf pdf;
application/postscript ps eps ai;
application/rtf rtf;
application/vnd.apple.mpegurl m3u8;
application/vnd.ms-excel xls;
application/vnd.ms-fontobject eot;
application/vnd.ms-powerpoint ppt;
application/vnd.wap.wmlc wmlc;
application/vnd.google-earth.kml+xml kml;
application/vnd.google-earth.kmz kmz;
application/x-7z-compressed 7z;
application/x-cocoa cco;
application/x-java-archive-diff jardiff;
application/x-java-jnlp-file jnlp;
application/x-makeself run;
application/x-perl pl pm;
application/x-pilot prc pdb;
application/x-rar-compressed rar;
application/x-redhat-package-manager rpm;
application/x-sea sea;
application/x-shockwave-flash swf;
application/x-stuffit sit;
application/x-tcl tcl tk;
application/x-x509-ca-cert der pem crt;
application/x-xpinstall xpi;
application/xhtml+xml xhtml;
application/xspf+xml xspf;
application/zip zip;
application/octet-stream bin exe dll;
application/octet-stream deb;
application/octet-stream dmg;
application/octet-stream iso img;
application/octet-stream msi msp msm;
application/vnd.openxmlformats-officedocument.wordprocessingml.document docx;
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx;
application/vnd.openxmlformats-officedocument.presentationml.presentation pptx;
audio/midi mid midi kar;
audio/mpeg mp3;
audio/ogg ogg;
audio/x-m4a m4a;
audio/x-realaudio ra;
video/3gpp 3gpp 3gp;
video/mp2t ts;
video/mp4 mp4;
video/mpeg mpeg mpg;
video/quicktime mov;
video/webm webm;
video/x-flv flv;
video/x-m4v m4v;
video/x-mng mng;
video/x-ms-asf asx asf;
video/x-ms-wmv wmv;
video/x-msvideo avi;
}
And that is all. The neccessary basics to do with the nginx core settings are mostly done. Now it is time to set up configuration for some websites. :D
It is recommended to configure each website separately in the `/etc/nginx/sites-available/` and link it to the `/etc/nginx/sites-enabled/` directory.
Now lets edit `/etc/nginx/sites-available/example.conf` file (change 'example' and use your own domain name here to know what is where later).
nano /etc/nginx/sites-available/example.conf
Inside of that file we need to put a lot of informations. Remember that we are going for a performance savy configuration here. In examples below I'll show you my two 'optimal' configurations I use for various websites.
Let's try something smaller at the beginning (you can extend it later):
VERSION 1(recommended):
server {
listen 80;
server_name www.example.com;
return 301 $scheme://example.com$request_uri;
}
server {
listen 80;
server_name example.com;
root /home/user/website/example;
index index.html index.htm;
charset utf-8;
location / {
index index.html index.htm;
autoindex on;
}
gzip on;
gzip_min_length 1000;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
access_log /var/log/nginx/localhost.access_log main;
error_log /var/log/nginx/error_log info;
location ~* \.html$ {
expires -1;
}
location ~* \.(jpg|jpeg|png|gif|ico|css|js|xml)$ {
access_log off;
log_not_found off;
expires 30d;
add_header Pragma public;
add_header Cache-Control "public, must-revalidate, proxy-revalidate";
}
}
VERSION 2 (extended):
# www to no www:
server {
listen 80;
server_name www.example.com;
return 301 $scheme://example.com$request_uri;
}
# no www
server {
# Listen on both IPv4 and IPv6
listen 443 ssl; # default deferred;
listen [::]:443 ipv6only=on ssl; # default deferred;
server_name example.com;
# Hide nginx version:
server_tokens off;
# SSL Cert location:
ssl_certificate /etc/nginx/ssl/example.com.pem;
# example.com.pem - KEY -important as hell!
ssl_certificate_key /etc/nginx/ssl/example.com.key;
# enable session resumption to improve https performance
# https://vincent.bernat.im/en/blog/2011-ssl-session-reuse-rfc5077.html
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 5m;
# Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits.
ssl_dhparam /etc/nginx/ssl/dhparam.pem;
# enables server-side protection from BEAST attacks
# https://blog.ivanristic.com/2013/09/is-beast-still-a-threat.html
ssl_prefer_server_ciphers on;
# disable SSLv3(enabled by default since nginx 0.8.19) since it's less secure then TLS
# https://en.wikipedia.org/wiki/Secure_Sockets_Layer#SSL_3.0
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
# ciphers chosen for forward secrecy and compatibility
# https://blog.ivanristic.com/2013/08/configuring-apache-nginx-and-openssl-for-forward-secrecy.html
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:RC4-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK';
# enable ocsp stapling (mechanism by which a site can convey certificate revocation information to visitors in a
# privacy-preserving, scalable manner)
# --> https://blog.mozilla.org/security/2013/07/29/ocsp-stapling-in-firefox/
# SSL Stapling(Doesn't work on cloudflare SSl Cert (no CA Cert Available)):
# ssl_trusted_certificate /etc/nginx/ssl/..??;
# resolver 8.8.8.8;
# ssl_stapling on;
# ssl_stapling_verify on;
# config to enable HSTS(HTTP Strict Transport Security)
# --> https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security
# to avoid ssl stripping https://en.wikipedia.org/wiki/Moxie_Marlinspike#SSL_stripping
add_header Strict-Transport-Security "max-age=31536000; includeSubdomains;";
# And here is the rest of the configuration.
# Change this location accordingly to the location where you keep your website:
root /home/user/website/example;
index index.html index.htm;
# Disable unwanted HTTP Methods:
if ($request_method !~ ^(GET|HEAD|POST)$) {
return 444;
}
# GeoIP
if ($allowed_country = no) {
return 444;
}
# End of GeoIP
charset utf-8;
# Only Cloudflare traffic through nginx:
location / {
index index.html index.htm;
try_files $uri $uri/ /index.html;
autoindex on;
}
gzip on;
gzip_min_length 1000;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
error_page 404 /404.html;
access_log /var/log/nginx/localhost.access_log main;
error_log /var/log/nginx/error_log info;
location /404.html {
# Change it accordingly to the location of website files:
root /home/user/website/example;
}
location ~* \.html$ {
expires -1;
}
location ~* \.(jpg|jpeg|png|gif|ico|css|js|xml)$ {
access_log off;
log_not_found off;
expires 30d;
add_header Pragma public;
add_header Cache-Control "public, must-revalidate, proxy-revalidate";
}
# Image hotlinking protection:
location ~ \.(jpg|jpeg|png|gif|ico|jpe?g)$ {
valid_referers none blocked example.com *.example.com;
if ($invalid_referer) {
return 403;
}
}
location ~* \.(gif|jpg|jpeg|png|wmv|ico|avi|mpg|mpeg|mp4|htm|html|js|css)$ {
valid_referers none blocked example.com www.example.com ~\.google\. ~\.yahoo\. ~\.bing\. ~\.facebook\. ~\.fbcdn\.;
if ($invalid_referer) {
return 403;
}
}
# Cache settings for static files directory:
location ~* /images/.+\.[0-9a-f][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]\. {
expires max;
}
}
# no www to https
# redirect all http traffic to https
server {
# Listen on both IPv4 and IPv6
listen 80;
listen [::]:80 ipv6only=on;
server_name example.com;
return 301 https://example.com$request_uri;
}
Start with the 'version 1' and configure it carefully. When you are happy with your results start adding changes from the 'version 2'
When your `example.conf` is done and save - it is time to link it to the `/etc/nginx/sites-enabled` directory.
ln -s /etc/nginx/sites-available/example.conf /etc/nginx/sites-enabled/aaaexample.conf
Above I've wrote aaaexample.conf
to set which websites is set as the first and main for nginx server. There is no logic behind that you can name it as you want as long as you know what you are doing.
All is set and in place (probably). Lets test it:
nginx -t
Eliminate all errors from your configs. run nginx -t
to the moment you have good working setup.
And push it online:
service nginx start
Finally add nginx to the start:
rc-update add nginx default