Friday, June 14, 2024
HomeHow toHow to Install Hugo Site Generator with Nginx on Rocky Linux 9

How to Install Hugo Site Generator with Nginx on Rocky Linux 9

A static site is built with pages of static content or plain HTML, JavaScript, or CSS code. The content is not dynamically generated and does not involve any database or server-side processing. Such sites are incredibly fast and immune to exploits that depend on dynamic code or SQL.

Hugo is an open-source static site generator written in Go. It generates content by converting posts written in markdown language to HTML. Hugo also ships with an in-built server to host the sites it generates. You can also host a Hugo website on Amazon S3, GitHub pages, Google Cloud, Netlify, Vercel, etc.

In this tutorial, we will explore how to install Hugo to create a static website on a server powered by Rocky Linux 9. We will use the Nginx server to host the site and the Let’s Encrypt SSL certificate to secure our installation.


  • A server running Rocky Linux 9.
  • A non-root user with sudo privileges.
  • A fully qualified domain name (FQDN) pointing to your server. For our purposes, we will use as the domain name.
  • Make sure everything is updated.
    $ sudo dnf update
  • Install basic utility packages. Some of them may already be installed.
    $ sudo dnf install wget curl nano unzip yum-utils tree -y

Step 1 – Configure Firewall

The first step is to configure the firewall. Rocky Linux uses Firewalld Firewall. Check the firewall’s status.

$ sudo firewall-cmd --state

The firewall works with different zones, and the public zone is the default one that we will use. List all the services and ports active on the firewall.

$ sudo firewall-cmd --permanent --list-services

It should show the following output.

cockpit dhcpv6-client ssh

Wiki.js needs HTTP and HTTPS ports to function. Open them.

$ sudo firewall-cmd --add-service=http --permanent
$ sudo firewall-cmd --add-service=https --permanent

Reload the firewall to apply the changes.

$ sudo firewall-cmd --reload

Step 2 – Install Nginx

Rocky Linux 9 ships with an older version of Nginx. You need to download the official Nginx repository to install the latest version.

Create and open the /etc/yum.repos.d/nginx.repo file for creating the official Nginx repository.

$ sudo nano /etc/yum.repos.d/nginx.repo

Paste the following code in it.

name=nginx stable repo

name=nginx mainline repo

Save the file by pressing Ctrl + X and entering Y when prompted.

Install the Nginx server.

$ sudo dnf install nginx -y

Verify the installation.

$ nginx -v
nginx version: nginx/1.24.0

Enable and start the Nginx server.

$ sudo systemctl enable nginx --now

Check the status of the server.

$ sudo systemctl status nginx
? nginx.service - nginx - high performance web server
     Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: disabled)
     Active: active (running) since Mon 2023-10-30 08:24:05 UTC; 7s ago
    Process: 4265 ExecStart=/usr/sbin/nginx -c /etc/nginx/nginx.conf (code=exited, status=0/SUCCESS)
   Main PID: 4266 (nginx)
      Tasks: 2 (limit: 4402)
     Memory: 1.9M
        CPU: 7ms
     CGroup: /system.slice/nginx.service
             ??4266 "nginx: master process /usr/sbin/nginx -c /etc/nginx/nginx.conf"
             ??4267 "nginx: worker process"

Oct 30 08:24:05 systemd[1]: Starting nginx - high performance web server...

Step 3 – Install Git

Hugo needs Git to work. Run the following command to install Git

$ sudo dnf install git -y

Verify Git installation.

$ git --version
git version 2.39.3

Step 4 – Install Hugo

Since Hugo is built using the Go language, installing it is quite easy since it doesn’t require any dependency and can be run standalone.

Download Hugo. For our tutorial, we will be installing the extended version of Hugo. The extended version can encode images to the Webp format when processing images. This is useful if you want Hugo to convert images for you on the fly and keep their sizes down. The extended version also includes a SASS compiler which can be useful in certain cases in case you need it.

Create a directory for Hugo.

$ sudo mkdir /var/www/html/hugo -p

Switch to the newly created directory.

$ cd /var/www/html/hugo

Download Hugo from the GitHub releases page. At the time of writing this tutorial, v0.119.0 is the latest version which is what we will download.

$ sudo wget

Extract the files from the archive.

$ sudo tar -xzf hugo_extended_0.119.0_linux-amd64.tar.gz

You will see the following three files in your directory.

$ ls
hugo  hugo_extended_0.119.0_linux-amd64.tar.gz  LICENSE

Move the hugo binary to the /usr/bin directory.

$ sudo mv hugo /usr/bin/hugo

The file already has executable permission set so you don’t need to set it.

Confirm the installation.

$ hugo version
hugo v0.119.0-b84644c008e0dc2c4b67bd69cccf87a41a03937e+extended linux/amd64 BuildDate=2023-09-24T15:20:17Z VendorInfo=gohugoio

You can also check all the available options for use by using the --help flag.

$ hugo --help

You will get the following output.

hugo is the main command, used to build your Hugo site.

Hugo is a Fast and Flexible Static Site Generator
built with love by spf13 and friends in Go.

Complete documentation is available at

  hugo [flags]
  hugo [command]

Available Commands:
  completion  Generate the autocompletion script for the specified shell
  config      Print the site configuration
  convert     Convert your content to different formats
  deploy      Deploy your site to a Cloud provider.
  env         Print Hugo version and environment info
  gen         A collection of several useful generators.
  help        Help about any command
  import      Import your site from others.
  list        Listing out various types of content
  mod         Various Hugo Modules helpers.
  new         Create new content for your site
  server      A high performance webserver
  version     Print Hugo version and environment info

  -b, --baseURL string             hostname (and path) to the root, e.g.
  -D, --buildDrafts                include content marked as draft
  -E, --buildExpired               include expired content
  -F, --buildFuture                include content with publishdate in the future
      --cacheDir string            filesystem path to cache directory
      --cleanDestinationDir        remove files from destination not found in static directories
      --clock string               set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
      --config string              config file (default is hugo.yaml|json|toml)
      --configDir string           config dir (default "config")
  -c, --contentDir string          filesystem path to content directory
      --debug                      debug output
  -d, --destination string         filesystem path to write files to
      --disableKinds strings       disable different kind of pages (home, RSS etc.)
      --enableGitInfo              add Git revision, date, author, and CODEOWNERS info to the pages
  -e, --environment string         build environment
      --forceSyncStatic            copy all files when static is changed.
      --gc                         enable to run some cleanup tasks (remove unused cache files) after the build
  -h, --help                       help for hugo
      --ignoreCache                ignores the cache directory
      --ignoreVendorPaths string   ignores any _vendor for module paths matching the given Glob pattern
  -l, --layoutDir string           filesystem path to layout directory
      --logLevel string            log level (debug|info|warn|error)
      --minify                     minify any supported output format (HTML, XML etc.)
      --noBuildLock                don't create .hugo_build.lock file
      --noChmod                    don't sync permission mode of files
      --noTimes                    don't sync modification time of files
      --panicOnWarning             panic on first WARNING log
      --poll string                set this to a poll interval, e.g --poll 700ms, to use a poll based approach to watch for file system changes
      --printI18nWarnings          print missing translations
      --printMemoryUsage           print memory usage to screen at intervals
      --printPathWarnings          print warnings on duplicate target paths etc.
      --printUnusedTemplates       print warnings on unused templates.
      --quiet                      build in quiet mode
      --renderToMemory             render to memory (only useful for benchmark testing)
  -s, --source string              filesystem path to read files relative from
      --templateMetrics            display metrics about template executions
      --templateMetricsHints       calculate some improvement hints when combined with --templateMetrics
  -t, --theme strings              themes to use (located in /themes/THEMENAME/)
      --themesDir string           filesystem path to themes directory
      --trace file                 write trace to file (not useful in general)
  -v, --verbose                    verbose output
  -w, --watch                      watch filesystem for changes and recreate as needed

Use "hugo [command] --help" for more information about a command.

Delete all the remaining files in the folder since they are no longer needed.

$ sudo rm -rf *.*

Step 5 – Create a new Hugo Site

Issue the following command to create a new Hugo website.

$ sudo hugo new site osgrove

You will get the following output.

Congratulations! Your new Hugo site was created in /home/navjot/hugo/osgrove.

Just a few more steps...

1. Change the current directory to /home/navjot/hugo/osgrove.
2. Create or install a theme:
   - Create a new theme with the command "hugo new theme <THEMENAME>"
   - Install a theme from
3. Edit hugo.toml, setting the "theme" property to the theme name.
4. Create new content with the command "hugo new content <SECTIONNAME>/<FILENAME>.<FORMAT>".
5. Start the embedded web server with the command "hugo server --buildDrafts".

See documentation at

Run the tree command to check the structure of the site.

$ tree osgrove/

You will get the following output.

??? archetypes
?   ???
??? assets
??? content
??? data
??? hugo.toml
??? i18n
??? layouts
??? static
??? themes

8 directories, 2 files

Switch to the Hugo directory.

$ cd osgrove

Step 6 – Install Hugo Theme

The next step is to install a Hugo theme. There are two ways to go about it and both of them require Git. The first method is plain and simple and involves cloning the Theme’s GitHub repository. This is useful in most cases and the easiest way to go about it. Updating via this method is easy though this method does involve downloading a lot of unnecessary stuff you don’t need. There are some themes, especially the paid ones, that don’t have a GitHub repository from where you can grab them. Such themes are usually offered as a compressed archive. In that case, you need to extract the archive manually and keep the theme updated manually. The third method involves grabbing the theme as a Git submodule. This requires you to initialize your Hugo directory as a Git repository. This is the recommended method and is useful in case you want to deploy your site to GitHub, Netlify, or any other hosting service. Pushing your Hugo site to GitHub ensures automatic backups as well without any extra effort.

We will cover all three methods. There is another method of installing a Hugo theme using Hugo Go modules. This method only works if the theme is available as a Hugo module. However, we will not be covering it since it is outside the scope of this article.

Method 1 – Cloning a Hugo Theme

This is the most commonly used method of installing a Hugo theme. For our tutorial, we will be installing the Paper theme. Switch to the Hugo directory.

$ cd /var/www/html/hugo/osgrove

Run the following command to clone the theme’s GitHub repository. The --depth=1 flag only grabs the latest commit.

$ sudo git clone themes/Paper --depth=1

To update the theme downloaded via this method, run the following commands.

$ cd ~/hugo/osgrove/themes/Paper
$ sudo git pull

Method 2 – Installing Theme from compressed archive

This is the easiest method of all. Grab the archive of the theme file. Paper theme doesn’t have a releases section so you can’t download a particular version. Therefore, we will download the GitHub master archive. You can grab the link from its GitHub repository page.

$ cd ~
$ wget

Extract the archive into the themes directory. The -q flag suppresses the output and the -d flag specifies the output directory.

$  sudo unzip -q -d /var/www/html/hugo/osgrove/themes/

To update the theme, you need to download the latest archive and extract it again. Make sure you take a backup of all the files you have changed.

The bottom two methods work only if your Hugo site is hosted as a Git project. Otherwise, you don’t need to use the following methods.

Method 3 – Installing Theme as a Git Submodule

Switch to the Hugo folder.

$ cd /var/www/html/hugo/osgrove

Initialize the Hugo folder as a Git repository. This is an important step. This allows you to push your Hugo site to GitHub for use later to host using Netlify, AWS, Vercel, etc.

$ sudo git init

Run the following command to add the theme as a Git submodule.

$ sudo git submodule add themes/paper

To update a theme added as a Submodule, run the following command from the Hugo folder.

$ sudo git submodule update --remote --merge

Configure Hugo

Once you have installed the theme using a method of your choice, open the hugo.toml file in your Hugo’s root directory. Starting with Hugo v0.110.0, the default configuration file is named hugo.toml. If you are using an older version of Hugo, you will find the file config.toml in your Hugo directory.

$ sudo nano hugo.toml

Add the following line to the file. The value should match the directory of the theme. For the paper theme, that would be the hugo-paper-theme directory if you used the archive installation or Paper if you used the Git clone or submodule method above.

theme = 'hugo-paper-main'

Also, change the value of the variable baseURL to your site’s domain name. And change the title of your website.

Your configuration file should look like the following.

baseURL = ''
languageCode = 'en-us'
title = 'osgrove Hugo Demo'
theme = 'hugo-paper-main'

Once finished, save the file by pressing Ctrl + X and entering Y when prompted.

Step 7 – Create Posts and Pages

To create a new post, issue the following command.

$ sudo hugo new post/

This will create a new markdown file with the title First Post in the content/post folder. Open the file in your favorite editor and edit out the details and write something for your first post.

title = 'First Post'
date = 2023-10-30T03:32:38Z
draft = true
## Introduction

This is **bold** text, and this is *emphasized* text.

Visit the [osgrove]( website!

Remove the draft = true once you have finished writing the post. Save the file by pressing Ctrl + X and entering Y when prompted.

To create a new page, issue the following command.

$ sudo hugo new

This will create a new page titled About in the content folder. Open it and edit it in the same way as the post.

Configure Theme

Before publishing, we need to make a few changes to the theme to show the menu bar. These changes will be done in Hugo’s configuration file and not the theme’s configuration.

Switch to the Hugo directory.

$ cd /var/www/html/hugo/osgrove

Open the hugo.toml file for editing.

$ sudo nano hugo.toml

Paste the following code at the bottom.

author = "Navjot Singh"
copyright = "Copyright © 2023, Navjot Singh; all rights reserved."
paginate = 3
enableInlineShortcodes = true
# needed to  render raw HTML (e.g. <sub>, <sup>, <kbd>, <mark>)
      unsafe = true

    identifier = "about"
    name = "About"
    url = "/about/"
    weight = 10

category = "categories"
tag = "tags"
series = "series"

Save the file by pressing Ctrl + X and entering Y when prompted.

These are some basic settings to make the site functional. Here we have configured the site to display 3 posts per page, show the About page as a menu item, and set up categories, tags, and post-series. We have also enabled Hugo’s inline shortcodes and enabled raw HTML for the Goldmark Markdown render. You can disable it if you don’t want to embed raw HTML in your posts.

Step 8 – Publish Hugo Site

To build and publish the site, just run the following command.

$ sudo hugo

You will get a similar output.

Start building sites …
hugo v0.119.0-b84644c008e0dc2c4b67bd69cccf87a41a03937e+extended linux/amd64 BuildDate=2023-09-24T15:20:17Z VendorInfo=gohugoio

                   | EN
  Pages            |  7
  Paginator pages  |  0
  Non-page files   |  0
  Static files     | 11
  Processed images |  0
  Aliases          |  3
  Sitemaps         |  1
  Cleaned          |  0

Total in 46 ms

This creates and publishes the static Hugo website in the public folder. These are the files we need to specify in the Nginx configuration for the site to work. Run the command again after making any change and your website will get updated in no time.

Step 9 – Install SSL

Before we proceed, we need to install the Certbot tool and install an SSL certificate for our domain.

To install Certbot, we will use the Snapd package installer. Snapd always carries the latest stable version of Certbot and you should use that.

Snapd tool requires the Epel repository to work.

$ sudo dnf install epel-release -y

We will use Snapd to install Certbot. Install Snapd.

$ sudo dnf install snapd -y

Enable and Start the Snap service.

$ sudo systemctl enable snapd.socket --now

Create necessary links for Snapd to work.

$ sudo ln -s /var/lib/snapd/snap /snap
$ echo 'export PATH=$PATH:/var/lib/snapd/snap/bin' | sudo tee -a /etc/profile.d/

Ensure that your version of snapd is up to date. Wait for a couple of seconds before issuing the following commands.

$ sudo snap install core
$ sudo snap refresh core

Install Certbot.

$ sudo snap install --classic certbot

Use the following command to ensure that the Certbot command can be run by creating a symbolic link to the /usr/bin directory.

$ sudo ln -s /snap/bin/certbot /usr/bin/certbot

Verify the installation.

$ certbot --version
certbot 2.7.3

Generate an SSL certificate.

$ sudo certbot certonly --nginx --agree-tos --no-eff-email --staple-ocsp --preferred-challenges http -m [email protected] -d

The above command will download a certificate to the /etc/letsencrypt/live/ directory on your server.

Generate a Diffie-Hellman group certificate.

$ sudo openssl dhparam -dsaparam -out /etc/ssl/certs/dhparam.pem 4096

Check the Certbot renewal scheduler service.

$ sudo systemctl list-timers

You will find snap.certbot.renew.service as one of the services scheduled to run.

NEXT                        LEFT          LAST                        PASSED    UNIT                         ACTIVATES
Wed 2023-10-29 10:00:17 UTC 57min left    Wed 2023-10-18 08:49:39 UTC 12min ago dnf-makecache.timer          dnf-makecache.service
Wed 2023-10-29 12:18:00 UTC 3h 15min left -                           -         snap.certbot.renew.timer     snap.certbot.renew.service
Thu 2023-10-30 00:00:00 UTC 14h left      Wed 2023-10-18 08:10:08 UTC 52min ago logrotate.timer              logrotate.service

Do a dry run of the process to check whether the SSL renewal is working fine.

$ sudo certbot renew --dry-run

If you see no errors, you are all set. Your certificate will renew automatically.

Step 10 – Configure Nginx

Create and open the file /etc/nginx/conf.d/hugo.conf for editing.

$ sudo nano /etc/nginx/conf.d/hugo.conf

Paste the following code in the hugo.conf file. Replace all instances of with your domain.

## enforce HTTPS
server {
  listen 80;
  listen [::]:80;
  return 301 https://$server_name$request_uri;

server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;

  root /home/navjot/hugo/osgrove/public;
  index index.html
  access_log /var/log/nginx/hugo.access.log;
  error_log /var/log/nginx/hugo.error.log;
  client_max_body_size 20m;
  gzip on;

  http2_push_preload on; # Enable HTTP/2 Server Push

  ssl_certificate     /etc/letsencrypt/live/;
  ssl_certificate_key /etc/letsencrypt/live/;
  ssl_trusted_certificate /etc/letsencrypt/live/;
  ssl_session_timeout 1d;

  # Enable TLS versions (TLSv1.3 is required upcoming HTTP/3 QUIC).
  ssl_protocols TLSv1.2 TLSv1.3;

  # Enable TLSv1.3's 0-RTT. Use $ssl_early_data when reverse proxying to
  # prevent replay attacks.
  # @see:
  ssl_early_data on;

  ssl_prefer_server_ciphers on;
  ssl_ecdh_curve X25519:prime256v1:secp384r1:secp521r1;
  ssl_session_cache shared:SSL:50m;
  ssl_session_tickets off;

  # OCSP Stapling ---
  # fetch OCSP records from URL in ssl_certificate and cache them
  ssl_stapling on;
  ssl_stapling_verify on;
  ssl_dhparam /etc/ssl/certs/dhparam.pem;

  add_header X-Early-Data $tls1_3_early_data;
  location / {
      try_files $uri $uri/ =404;
  # Media
  location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|mp4|ogg|ogv|webm|htc)$ {
      expires 30d;

  # CSS and Js
  location ~* \.(css|js)$ {
      expires 7d;

# This block is useful for debugging TLS v1.3. Please feel free to remove this
# and use the `$ssl_early_data` variable exposed by NGINX directly should you
# wish to do so.
map $ssl_early_data $tls1_3_early_data {
  "~." $ssl_early_data;
  default "";

The above configuration will redirect all HTTP requests to HTTPS and will serve as a proxy for Hugo service to serve it via your domain.

Save the file by pressing Ctrl + X and entering Y when prompted.

Open the file /etc/nginx/nginx.conf for editing.

$ sudo nano /etc/nginx/nginx.conf

Add the following line before the line include /etc/nginx/conf.d/*.conf;.

server_names_hash_bucket_size  64;

Save the file by pressing Ctrl + X and entering Y when prompted.

Verify your Nginx configuration.

$ sudo nginx -t

If you see no errors, it means you are good to go. Reload the Nginx server.

$ sudo systemctl reload nginx

Step 11 – Configure SELinux

Change the file security context for OpenEMR.

$ sudo semanage fcontext -a -t httpd_sys_rw_content_t "/var/www/html/hugo/osgrove/public(/.*)?"

Apply the policy.

$ sudo restorecon -Rv /var/www/html/hugo/osgrove/public/

Allow Nginx to connect to the network.

$ sudo setsebool -P httpd_can_network_connect 1

Step 12 – Run the Site

Now, you can verify your installation by opening in your web browser. You will get the page indicating a successful installation.

Step 13 – Update Hugo

Updating Hugo is pretty simple. Download the latest release from Hugo’s GitHub page.

$ cd /var/www/html/hugo
$ sudo wget

Extract the archive.

$ sudo tar -xzf hugo_extended_0.119.0_linux-amd64.tar.gz

And replace the Hugo binary with the latest version. The -f flag overwrites the binary without prompting. You can use the -i flag instead if you want to the prompt for overwriting.

$ sudo mv -f hugo /usr/bin/hugo

That’s all. Updating Hugo is as simple as replacing the binary.

Step 14 – Backup and Restore Hugo

To take a backup, create an archive of the entire Hugo folder.

$ cd /var/www/html/hugo
$ sudo tar -czvf osgrove.tar.gz osgrove/

To restore it on a new server, extract the archive and install the binary and you are good to go.

$ cd /var/www/html/hugo
$ sudo tar -xzf osgrove.tar.gz


This concludes our tutorial on how to set up the Hugo static site generator on your Rocky Linux 9 server using Nginx. If you have any questions or feedback, share them in the comments below.



Please enter your comment!
Please enter your name here

Most Popular