Install Polr with a LEMP Stack on Ubuntu 20.04

Updated on July 25, 2024
Install Polr with a LEMP Stack on Ubuntu 20.04 header image

Introduction

Polr is a free and open-source link shortener written in PHP and powered by the Lumen framework. It allows you to host your URL shortener and to gain control over your data. Its significant features include a management dashboard, detailed link analytics, and a robust API.

This tutorial guides you through the process of installing Polr on Ubuntu 20.04 LTS and setting up HTTPS with a free Let's Encrypt TLS certificate.

Prerequisites

This tutorial assumes you own a domain name such as example.com, and you have pointed it to the server IP address. If not, replace example.com with the server IP address.

Make sure to replace example.com in the code examples with your domain name or IP address.

1. Install PHP

At the time of this writing, the latest version of PHP compatible with Polr is 7.3. But, the official Ubuntu 20.04 repositories only offer PHP 7.4. Therefore, you must use another 3rd-party repository to install PHP 7.3.

ppa:ondrej/php is an Ubuntu repository developed by Ondřej Surý, a long-time Debian developer. It offers both PHP 5.6, PHP 7.0 – 7.4, and PHP 8.0. You can install PHP 7.3 now, and when Polr supports newer PHP versions, you can also install them.

  1. Log in to the server as a non-root sudo user via SSH.

  2. Add the ppa:ondrej/php repository.

     $ sudo LC_ALL=C.UTF-8 add-apt-repository -y ppa:ondrej/php
  3. Install PHP-FPM and other necessary PHP extensions.

     $ sudo apt -y install php7.3-cli php7.3-common php7.3-curl php7.3-fpm php7.3-json php7.3-mbstring php7.3-mysql php7.3-xml
  4. List all the time zones that your Ubuntu system supports. Use the Up / Down keys to move through the list, and press Q to exit.

     $ timedatectl list-timezones
  5. Copy an appropriate time zone from the list, for example, America/New_York. Then update your Ubuntu system with that time zone.

     $ sudo timedatectl set-timezone America/New_York
  6. Edit the main PHP configuration file to tell PHP to use the new time zone. This tutorial uses nano as the editor, but you can use another editor such as vim.

     $ sudo nano /etc/php/7.3/fpm/php.ini
  7. Find the line ;date.timezone =. Remove the semicolon and add your time zone. For example:

     date.timezone = America/New_York
  8. Save the configuration file and exit.

  9. To enhance the security of your server, create a dedicated user named polr as the user/group of PHP-FPM processes for Polr. This user also owns the Polr source code files/folders.

     $ sudo adduser polr

    Every time you want to add, delete, or update the source code files/folders, you need to switch to this user.

  10. Create the PHP-FPM configuration file from the default one.

     $ sudo cp /etc/php/7.3/fpm/pool.d/www.conf /etc/php/7.3/fpm/pool.d/polr.conf
  11. Rename the default file to disable it and keep it as a backup.

     $ sudo mv /etc/php/7.3/fpm/pool.d/www.conf /etc/php/7.3/fpm/pool.d/www.conf.default
  12. Edit the PHP-FPM configuration file.

     $ sudo nano /etc/php/7.3/fpm/pool.d/polr.conf

    In the configuration file, any line starting with ; is a comment.

  13. Search for the following settings, then:

    • Replace [www] with [polr]
    • Replace user = www-data with user = polr
    • Replace group = www-data with group = polr (do not touch the listen.group = www-data setting)

    Make sure the listen = /run/php/php7.3-fpm.sock setting does not start with ;. This setting makes PHP-FPM listen on a Unix socket specified by the /run/php/php7.3-fpm.sock file.

  14. Copy and paste the following settings to the end of the file.

     catch_workers_output = yes
     php_flag[display_errors] = off
     php_admin_value[error_log] = /var/log/fpm-php/polr/error.log
     php_admin_flag[log_errors] = on
    
     php_admin_value[session.save_path] = /var/lib/php/session.polr

    Those settings make PHP-FPM log error messages to the /var/log/fpm-php/polr/error.log file instead of displaying them to website users and store session data in the /var/lib/php/session.polr folder.

  15. Save the configuration file and exit.

  16. Create two folders to store PHP logs and session data.

     $ sudo mkdir -p /var/log/fpm-php/polr
     $ sudo mkdir -p /var/lib/php/session.polr
  17. Update the ownership and permissions of the two folders so that only the PHP-FPM processes of Polr can write to them.

     $ sudo chown polr:polr /var/log/fpm-php/polr
     $ sudo chmod 700 /var/log/fpm-php/polr
     $ sudo chown polr:polr /var/lib/php/session.polr
     $ sudo chmod 700 /var/lib/php/session.polr
  18. Check the new configuration.

     $ sudo php-fpm7.3 -t
  19. Restart the PHP-FPM service for the changes to take effect.

     $ sudo systemctl restart php7.3-fpm.service

2. Install MySQL

  1. The minimum version of MySQL supported by Polr is 5.5. However, you can install MySQL version 8.0 from the official Ubuntu 20.04 repositories to fulfill this requirement.

     $ sudo apt -y install mysql-server
  2. Run the mysql_secure_installation script to enhance the security of MySQL.

     $ sudo mysql_secure_installation

    To enforce strong passwords, press Y and Enter to set up the VALIDATE PASSWORD component. Then press 1 and Enter to select the MEDIUM password policy, which is secure enough.

    With this policy, MySQL requires all passwords to includes numeric, mixed case, and special characters. You can use a free password manager like KeePassXC or an online tool such as Random Password Generator to generate strong passwords.

    Enter a strong password twice for the MySQL root user. Then press Y and Enter to confirm.

    Press Y and Enter for any remaining questions to accept the recommended options.

  3. Connect to the MySQL command line as the MySQL root user.

     $ sudo mysql -u root
  4. Create a MySQL database named polr for Polr.

     mysql> CREATE DATABASE polr CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
  5. Create a MySQL user named polr for Polr. Replace password with a strong password.

     mysql> CREATE USER 'polr'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';
     mysql> GRANT ALL PRIVILEGES ON polr.* TO 'polr'@'localhost';
     mysql> FLUSH PRIVILEGES;
  6. Exit the MySQL command line.

     mysql> exit

3. Install Polr

Download the Source Code

  1. Retrieve the code from GitHub.

     $ cd ~ && wget https://github.com/cydrobolt/polr/archive/refs/tags/2.3.0b.zip

    At the time of this writing, the latest stable version of Polr is 2.3.0b. Of course, you can always visit the Polr releases page on GitHub to get the latest version.

  2. Install the unzip package to extract the archive.

     $ sudo apt -y install unzip
  3. Extract the archive.

     $ unzip 2.3.0b.zip
  4. Set polr as the owner of the source code folder.

     $ sudo chown -R polr:polr polr*
  5. Move the source code folder to /var/www/polr because, traditionally, the source code folders of websites are in the /var/www folder.

     $ sudo mkdir /var/www
     $ sudo mv polr* /var/www/polr

Install Dependencies

Polr requires Composer, the PHP dependency manager, to manage its dependencies.

  1. Install Composer with the following command.

     $ cd ~ && curl -sS https://getcomposer.org/installer | php
  2. Make the composer command globally available.

     $ sudo mv composer.phar /usr/local/bin/composer
  3. Switch to the polr user before installing Polr dependencies.

     $ sudo su polr
  4. Install dependencies with Composer.

     $ cd /var/www/polr && composer install --no-dev -o
  5. Copy the provided configuration file to enable the web-based installer.

     $ cp .env.setup .env
  6. Switch back to the sudo user to continue with the setup.

     $ exit

4. Install Nginx

  1. Install Nginx with the following command.

     $ sudo apt -y install nginx
  2. Disable the default configuration.

     $ sudo rm /etc/nginx/sites-enabled/default
  3. Create a new configuration file for Polr.

     $ sudo nano /etc/nginx/sites-available/polr-http.conf
  4. Paste the following contents:

     server {
       listen 80;
       listen [::]:80;
    
       server_name example.com;
    
       root  /var/www/polr/public;
       index index.html index.php;
    
       # All URLs are processed by index.php
       location / {
         try_files $uri $uri/ /index.php$is_args$args;
       }
    
       # Pass PHP files to PHP-FPM listening on /run/php/php7.3-fpm.sock
       location ~ \.php$ {
         try_files $uri =404;
    
         # Mitigate https://httpoxy.org/ vulnerabilities
         fastcgi_param HTTP_PROXY "";
    
         fastcgi_pass unix:/run/php/php7.3-fpm.sock;
         fastcgi_index index.php;
         include fastcgi.conf;
       }
    
       # Set expiration of assets to MAX for caching
       location ~* \.(jpg|jpeg|gif|png|css|js|ico|svg|eot|ttf|woff|woff2|otf)$ {
         expires max;
         log_not_found off;
       }
     }
  5. Save the configuration file and exit.

  6. Enable the new configuration.

     $ sudo ln -s /etc/nginx/sites-available/polr-http.conf /etc/nginx/sites-enabled/polr-http.conf
  7. Add the www-data user to the polr group so that Nginx processes can access the Polr source code folder.

     $ sudo usermod -aG polr www-data
  8. Check the new configuration.

     $ sudo nginx -t
  9. Reload the Nginx service for the changes to take effect.

     $ sudo systemctl reload nginx.service

5. (Optional) Configure HTTPS

If you own a valid domain name, you can set up HTTPS for your Polr at no cost. Using the Certbot program, you can get a free TLS certificate from Let's Encrypt, a certificate authority.

Install Certbot with Snap

Snap Store is an app store for Linux with millions of users. Ubuntu has built-in Snap Store support that makes it easy to get the latest version of Certbot with features like automatic certificate renewal.

$ sudo snap install --classic certbot

Make the certbot command globally available.

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

Get a Let's Encrypt Certificate

  1. Rename the HTTP configuration file to make it the template for the HTTPS configuration file.

     $ sudo mv /etc/nginx/sites-available/polr-http.conf /etc/nginx/sites-available/polr-https.conf
  2. Create a new configuration file to serve HTTP requests.

     $ sudo nano /etc/nginx/sites-available/polr-http.conf
  3. Paste the following contents:

     server {
       listen 80;
       listen [::]:80;
    
       server_name example.com;
    
       root /var/www/polr/public;
    
       location / {
           return 301 https://$server_name$request_uri;
       }
    
       location /.well-known/acme-challenge/ {}
     }

    This configuration makes Nginx redirect all HTTP requests, except those from Let's Encrypt, to corresponding HTTPS requests.

  4. Save the configuration file and exit. Then check the Nginx configuration.

     $ sudo nginx -t
  5. Apply the new configuration.

     $ sudo systemctl reload nginx.service
  6. Run the following command to get the Let's Encrypt certificate.

     $ sudo certbot certonly --webroot -w /var/www/polr/public -d example.com -m admin@example.com --agree-tos

    You may need to answer a question about sharing your email with the Electronic Frontier Foundation.

When finished, certbot tells you the path of your certificate file and key file:

/etc/letsencrypt/live/example.com/fullchain.pem
/etc/letsencrypt/live/example.com/privkey.pem

Another critical file, located in the same folder, also needed for the next step, is chain.pem.

Install the Certificate with Nginx

  1. Generate a file with DH parameters for DHE ciphers.

     $ sudo openssl dhparam -out /etc/nginx/dhparam.pem 2048

    2048 is the recommended size of DH parameters. This process may take a while, so please be patient.

  2. Update the HTTPS configuration file.

     $ sudo nano /etc/nginx/sites-available/polr-https.conf
  3. Find the following lines:

       listen 80;
       listen [::]:80;

    Replace them with:

       listen 443 ssl http2;
       listen [::]:443 ssl http2;
    
       ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
       ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    
       ssl_session_timeout 1d;
       ssl_session_cache shared:MozSSL:10m;  # about 40000 sessions
    
       # DH parameters file
       ssl_dhparam /etc/nginx/dhparam.pem;
    
       # intermediate configuration
       ssl_protocols TLSv1.2;
       ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
       ssl_prefer_server_ciphers off;
    
       # HSTS (ngx_http_headers_module is required) (63072000 seconds)
       #
       # Uncomment the following line only if your website fully supports HTTPS
       # and you have no intention of going back to HTTP, otherwise, it will
       # break your site.
       #
       # add_header Strict-Transport-Security "max-age=63072000" always;
    
       # OCSP stapling
       ssl_stapling on;
       ssl_stapling_verify on;
    
       # verify chain of trust of OCSP response using Root CA and Intermediate certs
       ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
    
       # Use Cloudflare DNS resolver
       resolver 1.1.1.1;
  4. Save the configuration file and exit.

  5. Enable the new configuration.

     $ sudo ln -s /etc/nginx/sites-available/polr-https.conf /etc/nginx/sites-enabled/polr-https.conf
  6. Check the Nginx configuration.

     $ sudo nginx -t
  7. Apply the new configuration.

     $ sudo systemctl reload nginx.service

Automate Renewal

Let's Encrypt certificates are valid for 90 days, so you must renew your TLS certificate at least once every three months. The Certbot installation automatically created a systemd timer unit to automate this task.

  1. Run the following command to verify the timer is active.

     $ sudo systemctl list-timers | grep 'certbot\|ACTIVATES'

    After renewing the certificate, Certbot will not automatically reload Nginx, so Nginx still uses the old certificate. Instead, you must write a script inside the /etc/letsencrypt/renewal-hooks/deploy folder to reload Nginx.

  2. Open your text editor.

     $ sudo nano /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
  3. Paste the following contents:

     #!/bin/bash
    
     /usr/bin/systemctl reload nginx.service
  4. Save and exit.

  5. Make the script executable.

     $ sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
  6. Test the renewal process with a dry run.

     $ sudo certbot renew --dry-run

This Vultr article explains all the above steps in more detail. This kind of TLS setup gives you an A on the SSL Labs test.

6. Complete the Polr Setup

  1. Restart the server.

     $ sudo reboot
  2. Wait a moment for the system to boot, then open the http://example.com link in your browser.

    Setup screen

    The Setup screen appears with many settings. Here are the fundamental ones:

    • Database Username: enter polr
    • Database Password: enter the database password you created in step 2
    • Database Name: enter polr
    • Application Protocol: enter https:// if you have set up HTTPS in step 5. If not, enter http://
    • Application URL: enter example.com
    • Shortening Permissions: choose between Anyone can shorten URLs and Only logged in users may shorten URLs
    • Admin Username: enter a desired name
    • Admin Password: enter a strong password

    For the remaining settings, customize them to fit your specific needs or accept their default values.

  3. Click the Install button at the bottom of the screen to start the installation. When finished, Polr redirects you to the Setup Complete screen.

Behind the scenes, Polr has just saved all the settings in the /var/www/polr/.env configuration file. If you have programming experience, you can edit this file to change the settings as needed.

Your Polr website is now ready. You may log in or access the home page to create shortened links.