Strapi is an open-source, headless Content Management System (CMS), built with the JavaScript programming language. Like other headless CMS’, Strapi doesn’t come with a frontend out of the box. It uses an API for its frontend which allows you to build the website using popular frameworks like React and Next.js. Based on a plugin system, Strapi is a flexible CMS whose admin panel and API are extensible – and whose every part is customizable to match any use case. Strapi also has a built-in user system to manage in detail what the administrators and end users have access to.

In this tutorial, you will learn how to install the community version of Strapi CMS on a Rocky Linux 9 server along with Nginx as a reverse proxy server.

Prerequisites

  • A server running Rocky Linux 9 with a minimum of 2GB RAM and 1 CPU Core.

  • A non-root user with sudo privileges.

  • A fully qualified domain name (FQDN) like strapi.example.com.

  • Make sure everything is updated.

    $ sudo dnf update
    
  • Few packages that your system needs.

    $ sudo dnf install wget curl nano unzip yum-utils -y
    

    Some of these packages may already be installed on your system.

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
running

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

Strapi needs HTTP and HTTPS ports to function. Open them.

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

Reload the firewall to apply the changes.

$ sudo firewall-cmd --reload

List all the services again.

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

You should get the following output.

cockpit dhcpv6-client http https ssh

Step 2 – Install and Configure PostgreSQL

Strapi works with PostgreSQL 11 and above. Rocky Linux 9 ships with PostgreSQL 13 by default. We will use PostgreSQL 15 for our tutorial.

Run the following command to add the PostgreSQL GPG key.

$ curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | sudo tee /usr/share/keyrings/postgresql-key.gpg >/dev/null

Install the PostgreSQL repository RPM file.

$ sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/pgdg-redhat-repo-latest.noarch.rpm

Disable the built-in PostgreSQL module.

$ sudo dnf -qy module disable postgresql

Now, you can install PostgreSQL using the command below.

$ sudo dnf install -y postgresql15-server

Initialize the database.

$ sudo /usr/pgsql-15/bin/postgresql-15-setup initdb

Enable the PostgreSQL service.

$ sudo systemctl enable postgresql-15

Start the PostgreSQL service.

$ sudo systemctl start postgresql-15

Check the status of the PostgreSQL service.

$ sudo systemctl status postgresql-15
? postgresql-15.service - PostgreSQL 15 database server
     Loaded: loaded (/usr/lib/systemd/system/postgresql-15.service; enabled; vendor preset: disabled)
     Active: active (running) since Wed 2023-02-01 13:21:27 UTC; 41min ago
       Docs: https://www.postgresql.org/docs/15/static/
    Process: 53088 ExecStartPre=/usr/pgsql-15/bin/postgresql-15-check-db-dir ${PGDATA} (code=exited, status=0/SUCCESS)
   Main PID: 53093 (postmaster)
      Tasks: 7 (limit: 5727)
     Memory: 45.7M
        CPU: 1.112s
     CGroup: /system.slice/postgresql-15.service
             ??53093 /usr/pgsql-15/bin/postmaster -D /var/lib/pgsql/15/data/
             ??53094 "postgres: logger "
             ??53095 "postgres: checkpointer "
             ??53096 "postgres: background writer "
             ??53098 "postgres: walwriter "
             ??53099 "postgres: autovacuum launcher "
             ??53100 "postgres: logical replication launcher "

Launch the PostgreSQL shell.

$ sudo -i -u postgres psql

Create the Strapi database.

postgres=# CREATE DATABASE strapidb;

Create the Strapi user and choose a strong password.

postgres-# CREATE USER strapiuser WITH PASSWORD 'Your_Password';

Change the database owner to the Strapi user.

postgres-# ALTER DATABASE strapidb OWNER TO strapiuser;

Exit the shell.

postgres-# q

Verify that your credentials work.

$ psql --username strapiuser --password --host localhost strapidb
Password:
psql (15.1)
Type "help" for help.

strapidb=>

Exit the shell by typing q.

Step 3 – Install Node.js

Rocky Linux 9 ships with Node v16 which is outdated. We will install the latest LTS version of Node which is v18 at the time of writing this tutorial.

Grab the Node v18 installer from Nodesource.

$ curl -fsSL https://rpm.nodesource.com/setup_18.x | sudo bash -

Install Node.js.

$ sudo dnf install nodejs -y

Verify the Node.js version.

$ node -v
v18.13.0

Step 4 – Install Strapi

Run the following command to install Strapi.

$ npx [email protected] howtoforge-project
Need to install the following packages:
  [email protected]
Ok to proceed? (y) y

Enter y to proceed with the installation. Next, you will be asked to choose the Installation type. Choose Custom to proceed and answer the questions as follows.

? Choose your installation type Custom (manual settings)
? Choose your preferred language JavaScript
? Choose your default database client postgres
? Database name: strapidb
? Host: 127.0.0.1
? Port: 5432
? Username: strapiuser
? Password: Your_Password
? Enable SSL connection: No

Depending on your requirements, you can either choose Typescript or JavaScript as the language for Strapi.

Once the installation is complete, you are ready to build your Strapi project.

Switch to the project directory.

$ cd howtoforge-project

Run the following command to build the project, including the Strapi Admin UI.

$ NODE_ENV=production npm run build

Start the Strapi server using the following command.

$ node ~/howtoforge-project/node_modules/.bin/strapi start

Your application should be visible on the URL http://:1337. But first, open the port in the firewall.

$ sudo firewall-cmd --permanent --add-port=1337/tcp
$ sudo firewall-cmd --reload

Once you open the URL, you should get the following screen.

<img alt="Strapi CMS Home" data-ezsrc="https://kirelos.com/wp-content/uploads/2023/04/echo/strapi-cms-home.png6448bc3746cd8.jpg" ezimgfmt="rs rscb5 src ng ngcb5" height="376" loading="lazy" referrerpolicy="no-referrer" src="data:image/svg xml,” width=”750″>

Press Ctrl C in the terminal to stop the server. You should delete the firewall rule because we won’t need it.

$ sudo firewall-cmd --permanent --remove-port=1337/tcp
$ sudo firewall-cmd --reload

Step 5 – Install and Configure PM2

Instead of starting the server manually, we can use PM2 (Process Manager 2) to manage the process and create a systemd service for the same.

Switch to the home directory.

$ cd ~

Install PM2.

$ sudo npm install [email protected] -g

Create and open the PM2 configuration file for editing.

$ sudo nano ecosystem.config.js

Paste the following content in the file. Make sure to enter the correct directory name along with Postgres credentials.

module.exports = {
  apps: [
    {
      name: 'strapi',
      cwd: '/home/navjot/howtoforge-project',
      script: 'npm',
      args: 'start',
      env: {
        NODE_ENV: 'production',
        DATABASE_HOST: 'localhost',
        DATABASE_PORT: '5432',
        DATABASE_NAME: 'strapidb',
        DATABASE_USERNAME: 'strapiuser',
        DATABASE_PASSWORD: 'Your_Password',
      },
    },
  ],
};

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

Run your Strapi instance in the background using PM2.

$ pm2 start ecosystem.config.js

You will get the following output.

                        -------------

__/\\\\\\____/\\____________/\\____/\\\\_____
 _/\/////////\_/\\\________/\\\__/\///////\___
  _/\_______/\_/\//\____/\//\_///______//\__
   _/\\\\\\/__/\\///\/\/_/\___________/\/___
    _/\/////////____/\__///\/___/\________/\//_____
     _/\_____________/\____///_____/\_____/\//________
      _/\_____________/\_____________/\___/\/___________
       _/\_____________/\_____________/\__/\\\\\\\_
        _///______________///______________///__///////////////__


                          Runtime Edition

        PM2 is a Production Process Manager for Node.js applications
                     with a built-in Load Balancer.

                Start and Daemonize any application:
                $ pm2 start app.js

                Load Balance 4 instances of api.js:
                $ pm2 start api.js -i 4

                Monitor in production:
                $ pm2 monitor

                Make pm2 auto-boot at server restart:
                $ pm2 startup

                To go further checkout:
                http://pm2.io/


                        -------------

[PM2] Spawning PM2 daemon with pm2_home=/home/navjot/.pm2
[PM2] PM2 Successfully daemonized
[PM2][WARN] Applications strapi not running, starting...
[PM2] App [strapi] launched (1 instances)
????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
? id  ? name      ? namespace   ? version ? mode    ? pid      ? uptime ? ?    ? status    ? cpu      ? mem      ? user     ? watching ?
????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
? 0   ? strapi    ? default     ? N/A     ? fork    ? N/A      ? 0s     ? 0    ? online    ? 0%       ? 0b       ? navjot   ? disabled ?
????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????

Applications running under PM2 restart automatically if they crash or get killed.

Create a startup systemd script using the following command.

$ pm2 startup

You will get the following output.

[PM2] Init System found: systemd
[PM2] To setup the Startup Script, copy/paste the following command:
sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u navjot --hp /home/navjot

Copy the command from the above output and run it.

$ sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u navjot --hp /home/navjot

You will get the following output.

                        -------------

__/\\\\\\____/\\____________/\\____/\\\\_____
 _/\/////////\_/\\\________/\\\__/\///////\___
  _/\_______/\_/\//\____/\//\_///______//\__
   _/\\\\\\/__/\\///\/\/_/\___________/\/___
    _/\/////////____/\__///\/___/\________/\//_____
     _/\_____________/\____///_____/\_____/\//________
      _/\_____________/\_____________/\___/\/___________
       _/\_____________/\_____________/\__/\\\\\\\_
        _///______________///______________///__///////////////__


                          Runtime Edition

        PM2 is a Production Process Manager for Node.js applications
                     with a built-in Load Balancer.

                Start and Daemonize any application:
                $ pm2 start app.js

                Load Balance 4 instances of api.js:
                $ pm2 start api.js -i 4

                Monitor in production:
                $ pm2 monitor

                Make pm2 auto-boot at server restart:
                $ pm2 startup

                To go further checkout:
                http://pm2.io/


                        -------------

[PM2] Init System found: systemd
Platform systemd
Template
[Unit]
Description=PM2 process manager
Documentation=https://pm2.keymetrics.io/
After=network.target

[Service]
Type=forking
User=navjot
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
Environment=PATH=/home/navjot/.local/bin:/home/navjot/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/usr/bin:/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
Environment=PM2_HOME=/home/navjot/.pm2
PIDFile=/home/navjot/.pm2/pm2.pid
Restart=on-failure

ExecStart=/usr/lib/node_modules/pm2/bin/pm2 resurrect
ExecReload=/usr/lib/node_modules/pm2/bin/pm2 reload all
ExecStop=/usr/lib/node_modules/pm2/bin/pm2 kill

[Install]
WantedBy=multi-user.target

Target path
/etc/systemd/system/pm2-navjot.service
Command list
[ 'systemctl enable pm2-navjot' ]
[PM2] Writing init configuration in /etc/systemd/system/pm2-navjot.service
[PM2] Making script booting at startup...
[PM2] [-] Executing: systemctl enable pm2-navjot...
Created symlink /etc/systemd/system/multi-user.target.wants/pm2-navjot.service → /etc/systemd/system/pm2-navjot.service.
[PM2] [v] Command successfully executed.
 --------------------------------------- 
[PM2] Freeze a process list on reboot via:
$ pm2 save

[PM2] Remove init script via:
$ pm2 unstartup systemd

Save the PM2 process list.

$ pm2 save
[PM2] Saving current process list...
[PM2] Successfully saved in /home/navjot/.pm2/dump.pm2

Your Strapi service is now running in the background in production mode.

Step 6 – 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.

[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true

[nginx-mainline]
name=nginx mainline repo
baseurl=http://nginx.org/packages/mainline/centos/$releasever/$basearch/
gpgcheck=1
enabled=0
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true

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

Install the Nginx server.

$ sudo dnf install -y nginx

Verify the installation.

$ nginx -v
nginx version: nginx/1.22.1

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; vendor preset: disabled)
     Active: active (running) since Wed 2023-02-01 15:01:29 UTC; 7s ago
       Docs: http://nginx.org/en/docs/
    Process: 4637 ExecStart=/usr/sbin/nginx -c /etc/nginx/nginx.conf (code=exited, status=0/SUCCESS)
   Main PID: 4638 (nginx)
      Tasks: 2 (limit: 10884)
     Memory: 1.9M
        CPU: 8ms
     CGroup: /system.slice/nginx.service
             ??4638 "nginx: master process /usr/sbin/nginx -c /etc/nginx/nginx.conf"
             ??4639 "nginx: worker process"

Step 7 – Install SSL

We need to install Certbot to generate the SSL certificate.

We will use the Snapd package installer for that. Since Rocky Linux doesn’t ship with it, install the Snapd installer. It requires the EPEL repository to work.

$ sudo dnf install -y epel-release

Install Snapd.

$ sudo dnf install -y snapd

Enable and Start the Snap service.

$ sudo systemctl enable snapd --now

Install the Snap core package, and ensure that your version of Snapd is up to date.

$ sudo snap install core && sudo snap refresh core

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/snapd.sh

Issue the following command to 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.2.0

Run the following command to generate an SSL Certificate.

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

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

Generate a Diffie-Hellman group certificate.

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

To check whether the SSL renewal is working fine, do a dry run of the process.

$ sudo certbot renew --dry-run

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

Step 8 – Configure Nginx

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.

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

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

Paste the following code in it.

server {
  # Redirect any http requests to https
  listen         80;
  listen         [::]:80;
  server_name    strapi.example.com;
  return 301     https://$host$request_uri;
}

server {
  listen                    443 ssl http2;
  listen                    [::]:443 ssl http2;
  server_name               strapi.example.com;

  access_log                /var/log/nginx/strapi.access.log;
  error_log                 /var/log/nginx/strapi.error.log;

  # TLS configuration
  ssl_certificate           /etc/letsencrypt/live/strapi.example.com/fullchain.pem;
  ssl_certificate_key       /etc/letsencrypt/live/strapi.example.com/privkey.pem;
  ssl_trusted_certificate   /etc/letsencrypt/live/strapi.example.com/chain.pem;
  ssl_protocols             TLSv1.2 TLSv1.3;

  ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384';
  ssl_prefer_server_ciphers on;
  ssl_session_cache         shared:SSL:50m;
  ssl_session_timeout       1d;

  # 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;

  location / {
    proxy_set_header        X-Real-IP $remote_addr;
    proxy_set_header        X-Forwarded-Proto $scheme;
    proxy_set_header	    X-Forwarded-Host $http_host;
    proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_pass              http://127.0.0.1:1337;
  }
}

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

Verify the Nginx configuration file syntax.

$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Restart the Nginx service.

$ sudo systemctl restart nginx

You can now access Strapi CMS via the URL https://strapi.example.com. You will see the following page which shows that Strapi is running in production mode.

<img alt="Strapi Production Home" data-ezsrc="https://kirelos.com/wp-content/uploads/2023/04/echo/strapi-production-home.png6448bc376e767.jpg" ezimgfmt="rs rscb5 src ng ngcb5" height="370" loading="lazy" referrerpolicy="no-referrer" src="data:image/svg xml,” width=”712″>

Visit the https://strapi.example.com/admin URL to create an administrator user.

<img alt="Strapi Admin User Create Page" data-ezsrc="https://kirelos.com/wp-content/uploads/2023/04/echo/strapi-admin-user-create-page.png6448bc37aa18f.jpg" ezimgfmt="rs rscb5 src ng ngcb5" height="750" loading="lazy" referrerpolicy="no-referrer" src="data:image/svg xml,” width=”559″>

Fill in your admin details and click the Let’s start button to proceed to the administrator dashboard screen.

<img alt="Strapi Admin Dashboard" data-ezsrc="https://kirelos.com/wp-content/uploads/2023/04/echo/strapi-admin-dashboard.png6448bc3804da4.jpg" ezimgfmt="rs rscb5 src ng ngcb5" height="417" loading="lazy" referrerpolicy="no-referrer" src="data:image/svg xml,” width=”750″>

From here on, you can start creating content on Strapi.

Step 9 – Upgrade Strapi

The first step in upgrading Strapi is to stop the server.

$ cd ~
$ pm2 stop ecosystem.config.js

Switch to the project directory and open the package.json file for editing.

$ cd howtoforge-project
$ nano package.json

Upgrade all of the Strapi package version numbers to the latest stable Strapi version. You can get the latest available version from Strapi’s GitHub releases page.

"devDependencies": {},
  "dependencies": {
    "@strapi/strapi": "4.5.5",
    "@strapi/plugin-users-permissions": "4.5.5",
    "@strapi/plugin-i18n": "4.5.5",
    "pg": "8.6.0"
  },

Here you need to change 4.5.5 to the latest stable version. Save the file by pressing Ctrl X and entering Y when prompted once finished.

Install the upgraded version.

$ npm install

Rebuild the Administration panel.

$ NODE_ENV=production npm run build

Start the server again.

$ cd ~
$ pm2 start ecosystem.config.js

Your Strapi installation is now upgraded and running.

Conclusion

This concludes our tutorial on installing Strapi CMS on a Rocky Linux 9 server along with Nginx as a reverse proxy server. If you have any questions, post them in the comments below.