For me the toughest and most time consuming part of web development was always front end. All the centering divs, box models and CSS inheritance stuff. And even if I get all the code to work, website still looks like highschool student’s project from the 90s.

Well, no more. Hugo framework allows users to create websites using Markdown language, and takes care of rendering Markdown files into HTML and CSS code.

In this article I will show how to set up a Nginx web server on RHEL (Alma Linux) and host a Hugo website on it.

Step 1: Buying a domain

From all the available domain regstrars I chose Cloudflare because:

  • Best prices overall if you take into account long term usage (>3 years).
  • Easy and very cutstomizable DNS management.
  • It supports proxied DNS, which means that you can use Cloudflare’s CDN to speed up your website and get better security.
  • Supports TLS certificatis creation and renewal.
  • Captcha can be turned on in case of a DDOS attack.
  • Apart from the domain name costs all other features are free!

Step 2: Installing Nginx

The choice of a cloud provider and operating system is completely subjective, anything will basically do. I chose AlmaLinux because I am a big fan of RHEL Linux systems (not trying to start a fight about which distro is better).

Before doing anything though, it’s best to update OS and configure the firewall.

# OS update and firewalld configuration
dnf update -y
dnf install -y firewalld
systemctl start --now firewalld
firewall-cmd --add-service https
firewall-cmd --add-service https --permanent

AlmaLinux’s repository, just like most of the most popular Linux distributions, doesn’t have the latest Nginx version, so we’re going to grab it from the official repository.

# adding Nginx repository
cat <<EOF > /etc/yum.repos.d/Nginx.repo
baseurl =\$releasever/\$basearch/
enabled = 1
gpgcheck = 0
name = nginx repo

dnf install -y nginx

Step 3: Installing Hugo

Unfortunately, hugo is also not present in the AlmaLinux repository. We can go nuts and compile hugo from source code (it’s actually just 2 commands + some compile time), but for the sake of simplicity we will use the official pre-compiled binary. Here is a small bash script to download and install the latest Hugo release:

# Download the latest tar.gz archive
curl -s $git_api_url \
        | grep -E "browser_.*_Linux-64bit.tar.gz" \
        | head -n 1 \
        | cut -d : -f 2,3 \
        | tr -d \" \
        | wget -qi - -O ./hugo_latest.tar.gz
echo "[Hugo binary downloaded]"

# Unpack the archive
mkdir hugo_latest
tar xf hugo_latest.tar.gz -C ./hugo_latest
cp ./hugo_latest/hugo /usr/local/bin
echo "[Hugo binary unpacked and installed]"

# Clean-up
rm -rf hugo_latest*
echo "[Residual files deleted]"

Script places hugo executable to /usr/local/bin, which should be in $PATH variable already. To test if installation was successful, run:

hugo --help

Now it’s time to create a website. Website folder will be located in /opt/hugo_site directory, so first navigate to /opt, create a new site with hugo comand and change ownership of the public directory to nginx user. This is necessary because Nginx will be serving static files from this directory.

# create site and change 'public' directory ownership
cd /opt
hugo new site hugo_site
cd hugo_site
chown nginx:nginx public

Hugo supports a lot of themes and it might be hard to choose one. Generally, it’s best to choose a theme that is well maitaing (has recent commits in Github repo) and has a good documentation. For this project I chose PaperMod and it will be installed as a git submodule.

# install Hugo theme
git submodule add --depth=1 themes/PaperMod
git submodule update --init --recursive
echo 'theme = "PaperMod"' >> config.toml

The last command will render static content into public directory, which will be used as location for Nginx config in the next step.

Step 4: Launching Website

With Nginx installed and static files created in /opt/hugo_site/public, let’s launch the website right away and see how it looks!
For that, create a symlink in /var/www directory to point to the static files, and then create a basic Nginx configuration file for the website.

# create symlink and Nginx config
ln -s /opt/hugo_site/public /var/www/
cat <<EOF > /etc/nginx/conf.d/
server {
    listen 80;
    listen [::]:80;


    root /var/www/;
    index index.html;
    location / {
        try_files $uri $uri/ =404;

After that, check if Nginx syntax is valid, and reload Nginx service:

# validate Nginx configs syntax and reload service
nginx -t
systemctl reload nginx

If there were no errors, website should be available in the browser at Or it can be tested with curl without leaving the server:

# get the web page

Step 5: Configuring TLS

Encrypting traffic is a must these days, even if it’s a simple static website. Thankfully, Cloudflare makes creating certificates as easy as clicking couple buttons in the web admin panel.

Is TLS encryption even required if Cloudflare proxy is turned on? Of course it is! Cloudflare proxy is encrypting traffic between client and the proxy, but traffic between proxy and the server is still unencrypted.

Let’s start by creating Origin Certificate - a TLS certificate signed by Cloudflare to authenticate your server.
In the Cloudfare admin page go to:
<zone_name> -> SSL/TLS -> Origin Server -> Create Certificate
After following default steps Cloudflare will generate the certificate and a private key. Both of them should be copied to the server with correct permissions.

# copy certificates
/etc/ssl/domain_name.com_cert.pem # copy certificate content to this file
/etc/ssl/domain_name.com_key.pem  # copy key content to this file 
chmod 600 /etc/ssl/domain_name.com_{cert,key}.pem

After certificate and key files are placed on the server, full encryption mode should be turned on.
Steps: <zone_name> -> SSL/TLS -> Origin Server -> Overview -> Full (strict)

To make the website even more secure, let’s add an additional certificate to verify that all traffic from the web server is received from Cloudflare’s infrastructure. Steps: <zone_name> -> SSL/TLS -> Origin Server -> Authenticated Origin Pulls

After turning origin pulls, go back to the server and download Cloudflare’s certificate that will be used for the verification.

# download Cloudflare's certificate
wget -O /etc/ssl/cloudflare.pem
chmod 600 /etc/ssl/cloudflare.pem

After this step, all requests to the website that are not from Cloudflare will get 400 error.

Lastly, let’s update Nginx’s configuration file to use TLS encryption:

# Nginx configuration file with TLS settings
server {
    listen 80;
    listen [::]:80;

    return 302 https://$server_name$request_uri;

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    ssl_certificate        /etc/ssl/domain_name.com_cert.pem;
    ssl_certificate_key    /etc/ssl/domain_name.com_key.pem;
    ssl_client_certificate /etc/ssl/cloudflare.pem;
    ssl_verify_client on;
    root /var/www/;
    index index.html;
    location / {
        try_files $uri $uri/ =404;

Validate Nginix config syntax and reload the service if there are not mistakes

# validate Nginx configs syntax and reload service
nginx -t
systemctl reload nginx

Enter website URL in the browser and check if the website is working with TLS encryption. If it works a green lock icon should appear in the browser next to the URL input field.


  • [Link] Registrar for Everyone
  • [Link] Hugo Quickstart
  • [Link] PaperMod Theme Wiki
  • [Link] Nginx Repository Installation
  • [Link] How To Host a Website Using Cloudflare and Nginx on Ubuntu 20.04