Markdown Blog

Setup Blog using Hugo static site generator, Nginx, LetsEncrypt

For this blog, i’ve setup a linux KVM vps, using as distro Fedora 27. Fedora 27 comes with Selinux enforced security policy by default this is OK for system or apps where you required a high security level, but for my case to serve a static blog and run a couple of services like OpenSSH and Nginx is ok to set the security policy on permissive mode.

changing the policy can be done via command or setup in permanent way on the selinux config in /etc/selinux/config

set selinux in permissive mode from the command line:

sudo setenforce=0

# to verify the security policy mode 

like this on /etc/selinux/config:

# This file controls the state of SELinux on the system.
# SELINUX= can take one of these three values:
#     enforcing - SELinux security policy is enforced.
#     permissive - SELinux prints warnings instead of enforcing.
#     disabled - No SELinux policy is loaded.
# SELINUXTYPE= can take one of these three values:
#     targeted - Targeted processes are protected,
#     minimum - Modification of targeted policy. Only selected processes are protected. 
#     mls - Multi Level Security protection.

for Nginx we define a config like this initially:

server {
    listen 80;
    listen [::]:80;

    root /srv/www;
    location / {
        index index.html;
        autoindex off;

we are going to use the directory /srv/www/ to server the blog files and also define in the hosts file the alias for this domain

# /etc/hosts
<IP address> 

for the firewall we use firewalld

# check the firewall service status
sudo systemctl status firewalld

# enable firewall service if it's no active
sudo systemctl enable firewalld

# add services and ports, 
# if openssh is not enable can be added in the same way 
sudo firewall-cmd --zone=public --permanent --add-service=https 
sudo firewall-cmd --zone=public --permanent --add-service=http 
sudo firewall-cmd --zone=public --permanent --add-port 443/tcp
sudo firewall-cmd --zone=public --permanent --add-port 80/tcp

# verify the firewall rules
sudo firewall-cmd --state

# verify the enable services
sudo firewall-cmd --zone=public --list-services 

for OpenSSH it needs to be setup to allow only public key authentication

the relevant option to add to /etc/ssh/sshd_config are

PasswordAuthentication no
PubkeyAuthentication yes

hugo can be found on the fedora packages and can be installed like this in the local machine

sudo dnf install hugo 

now we build the blog using hugo command on the local machine and rsync to upload the files to the server:

# on the blog directory
hugo -v && rsync -avz --delete public/ [email protected]:/srv/www/

this could be automated using a script like this:


SERVER_USERNAME="your username"
SERVER_HOSTNAME="your server ip or hostname"

hugo -v && \ 
rsync -avz --delete public/ ${SERVER_USERNAME}@${SERVER_HOSTNAME}:${DEST}
# Blog files

Next step is create a SSL certificate using LetsEncrypt, LetsEncrypt is available on Fedora we only need install the package and run with the params to generate the certificate for the domain and pass the verification.

sudo dnf install letsencrypt

sudo letsencrypt --text --email [email protected] \
--domains,, \
--agree-tos --renew-by-default --manual certonly

once the certificate has been generated is ready to use on /etc/letsencrypt/live/

the last step is update the Nginx configuration to use the new certificate

server {
    listen 80;
    listen [::]:80;
    access_log off;
    error_log off;
    return 301 https://$server_name/$request_uri;

server {
    listen 443 http2 ssl;
    listen [::]:443 http2 ssl;
    client_max_body_size 20M;
    root /srv/www;

    ssl_certificate /etc/letsencrypt/live/;
    ssl_certificate_key /etc/letsencrypt/live/;

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    ssl_ecdh_curve secp384r1;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off;

    ssl_stapling on;
    ssl_stapling_verify on;
    #ssl_trusted_certificate /etc/ssl/private/ca-certs.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/;
    resolver valid=300s;
    resolver_timeout 5s;

    add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;

    ssl_dhparam /etc/ssl/certs/dhparam.pem;

    location / {
        index index.html;
        autoindex off;

    location ~*  \.(jpg|jpeg|png|gif|ico|css|js)$ {
        expires 8d;

    error_page 404 /404.html;
    location = /40x.html {}

bonus: to renew automatically the certificate with letsencrypt we can use a cronb job like this:

# checking every monday at 2:30 if the certificate needs to be renewed. 
# then 5 min later restart nginx
30 2 * * 1 /bin/certbot renew >> /var/log/le-renew.log 
35 2 * * 1 /bin/systemctl restart nginx