Without going in too much details, I have been asked to build a server with certain requirements witch let me to that project and I thought it might be a good idea to describe the process as a reference as well as something to help others.
The assumption here is that you know how to install Debian, beware there are some changes if this is your first try on Debian 9. Make sure you know your network interfaces names, use the ip command or dmesg to find them out. Also as this setup is with DDNS make sure before you sign up for a DDNS service to check if the domain you want is included in the DNS Public Suffix List, otherwise you will not be able to use Let’s Encrypt services. You can check it here. I have used one of noip.com listed once.
Before everything else I installed the NO-IP’s DUC software. There is a good bash script to help you with that, which you can find here.
It did not quite work for me, I was missing the killall command, which can be installed with apt install psmisc and also on restart the service was not active, so what I did is to add a symbolic link in my rc.3 folder pointing to the script in /etc/init.d folder. You can also follow the guide on NO-IP’s web site and see how it goes.
Then comes Nginx, to install it run
apt install nginx,
in /etc/nginx/nginx.conf I changed the keep alive parameter down to 25.
Once the installation is finished next comes MySQL. MySQL is not Debian’s default database any more, so to install it you have to have the sources in your apt repository list. Navigate to https://dev.mysql.com/downloads/repo/apt/ to check on the latest version and correct download link, at the time of writing 0.8.10-1. Go to your download folder or create one and run
wget https://dev.mysql.com/get/mysql-apt-config_0.8.10-1_all.deb
and install it using dpkg:
dpkg -i mysql-apt-config_0.8.10-1_all.deb
Now having the right information in sources.list file we can install MySQL:
apt update
apt install mysql-community-client mysql-community-server
To have PHP installed run:
apt install php7.0-fpm php7.0-gd php-pear php7.0-mysqlnd php7.0-curl php7.0-intl php-imagick php7.0-imap php7.0-mcrypt php-memcache php7.0-intl php7.0-pspell php7.0-recode php7.0-tidy php7.0-xmlrpc php7.0-xsl
Have a look at the file /etc/php/7.0/fpm/php.ini if you need to change something, probably at least date.timezone to match your location.
Reload PHP: systemctl reload php7.0-fpm.service
Not much will be discussed here on MySQL and PHP. I wanted them installed for future development on multi level user login and file management, but in this instance I have only used the built in basic authentication in Nginx. (P.S. After finishing reading you can now check the next post where this is discussed.)
Before that though we will generate Let’s Encrypt certificates and this will be done using the default Nginx configuration. With your preferred editor open the file /etc/nginx/sites-available/default and set your domain name in the server_name line as well as the web root folder if different from default. The line should look like this after editing:
server_name yourhost.provider.com;
Replace yourhost.provider.com with your own domain name and then install Certbot:
apt install certbot python-certbot-nginx
Now we request an SSL certificate from Let’s Encrypt:
certbot certonly –webroot -d yourhost.provider.com
Read carefully the questions and give the appropriate answers. The newly generated SSL certificate is in a subfolder of /etc/letsencrypt/live/ folder. The exact path is shown in the Certbot output. Now it is time to create your own Nginx web server file. In mine I have the HTTP traffic redirected to the HTTPS, the web root denied for everyone so no one sees its content, two folders for two users and autoindex option on so users can see the file content in the folders. This is the configuration file:
server {
listen [::]:80;
listen 80;
server_name yourhost.provider.com;
# redirect http to https
return 301 https://yourhost.provider.com$request_uri;
}
server {
listen [::]:443 ssl http2;
listen 443 ssl http2;
server_name yourhost.provider.com;
root /var/www/html;
index index.php index.html index.htm;
ssl on;
ssl_certificate_key /etc/letsencrypt/live/yourhost.provider.com/privkey.pem;
ssl_certificate /etc/letsencrypt/live/yourhost.provider.com/fullchain.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ‘ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS’;
ssl_prefer_server_ciphers on;
# Rest of the config
location = / {
deny all;
}
location ^~ /test1/ {
auth_basic “Restricted Access”;
auth_basic_user_file /etc/nginx/users/.test1;
try_files $uri $uri/ =404;
autoindex on;
}
location ^~ /test2/ {
auth_basic “Restricted Access”;
auth_basic_user_file /etc/nginx/users/.test2;
try_files $uri $uri/ =404;
autoindex on;
}
# pass PHP scripts to FastCGI server
location ~ \.php$ {
include snippets/fastcgi-php.conf;
# With php-fpm (or other unix sockets):
fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
# With php-cgi (or other tcp sockets):
# fastcgi_pass 127.0.0.1:9000;
}
# Deny all attempts to access hidden files such as .htaccess, .htpasswd, .DS_Store (Mac).
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
}
It is useful to run nginx -t before reloading the service with systemctl restart nginx.service.
To create the files with the Nginx users and passwords I used the handy python script htpasswd.py, but if you are having troubles running it you are very likely to be missing the Trac package, which you can easily install:
apt install trac
To actually get the script just do:
cd /usr/local/bin
wget https://trac.edgewall.org/export/16537/trunk/contrib/htpasswd.py
chmod 755 /usr/local/bin/htpasswd.py
though check the Trac website for the latest download link.
To create a file /in my case/ do:
htpasswd.py -c -b /etc/nginx/users/.test1 user password
Do not use -c option if you want to add more users to the same file.
Finally it is not a bad idea to add firewall rules, these are my set as a bash script, but you can always change it the way it suits your needs. I use iptables-persistent package to save the rules and load them at boot:
#!/bin/bash
# Flush previous rules, delete chains and reset counters
iptables -F
iptables -X
iptables -Z
iptables -t nat -F
# INPUT
# Allows all loopback (lo0) traffic and drop all traffic to 127/8 that doesn’t use lo0
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT ! -i lo -d 127.0.0.0/8 -j REJECT
# Accepts all established inbound connections
iptables -A INPUT -m conntrack –ctstate ESTABLISHED,RELATED -j ACCEPT
# Allows HTTP and HTTPS connections from anywhere (the normal ports for websites)
iptables -A INPUT -p tcp –dport 80 -j ACCEPT
iptables -A INPUT -p tcp –dport 443 -j ACCEPT
# Allows SSH connections
# The –dport number is the same as in /etc/ssh/sshd_config
iptables -A INPUT -p tcp -s 192.168.0.0/16 -m conntrack –ctstate NEW,ESTABLISHED –dport 22 -j ACCEPT
# Allow Samba connections
iptables -A INPUT -p tcp -s 192.168.0.0/16 –dport 445 -j ACCEPT
iptables -A INPUT -p tcp -s 192.168.0.0/16 –dport 139 -j ACCEPT
iptables -A INPUT -p udp -s 192.168.0.0/16 –dport 137 -j ACCEPT
iptables -A INPUT -p udp -s 192.168.0.0/16 –dport 138 -j ACCEPT
# Allow ping
# note that blocking other types of icmp packets is considered a bad idea by some
# remove -m icmp –icmp-type 8 from this line to allow all kinds of icmp:
# https://security.stackexchange.com/questions/22711
iptables -A INPUT -p icmp -j ACCEPT
# log iptables denied calls (access via ‘dmesg’ command)
iptables -A INPUT -m limit –limit 5/min -j LOG –log-prefix “iptables denied: ” –log-level 7
# REST
# Drop invalid state packets
iptables -A INPUT -m conntrack –ctstate INVALID -j DROP
iptables -A OUTPUT -m conntrack –ctstate INVALID -j DROP
iptables -A FORWARD -m conntrack –ctstate INVALID -j DROP
# Reject all other inbound – default deny unless explicitly allowed policy:
iptables -A INPUT -j REJECT
iptables -A FORWARD -j REJECT
# Allows all outbound traffic
# You could modify this to only allow certain traffic
iptables -A OUTPUT -j ACCEPT
And that’s it, if I haven’t forgotten something… 🙂