Custom Domain and Security for ShinyProxy with Nginx
Tags: shinyproxy, shiny, rstats, docker
Published: 2021-08-23 15:44:53

*By: [Peter Solymos](https://hosting.analythium.io/author/peter/)*

**Here are the final steps to make your ShinyProxy instance secure and production-ready.**

This is the last post in this ShinyProxy series where I describe how to configure ShinyProxy with Nginx using HTTPS, set up certificate renewals, and a custom domain name.

Configuring ShinyProxy is a complex topic, that is the reason I have devoted quite a few posts to it including installation, configuration, and maintenance:

-   [How to Set Up ShinyProxy to Host Shiny Apps](https://hosting.analythium.io/how-to-set-up-shinyproxy-to-host-shiny-apps/)
-   [Advanced Configuration for ShinyProxy](https://hosting.analythium.io/advanced-configuration-for-shinyproxy/)
-   [Update Existing Shiny Apps in ShinyProxy](https://hosting.analythium.io/update-existing-shiny-apps-in-shinyproxy/)
-   [The Quickest Way to Add New Apps to ShinyProxy](https://hosting.analythium.io/the-quickest-way-to-add-new-apps-to-shinyproxy/)

Let's see what else is to be done to have your very own, production-ready, and secure ShinyProxy instance, just like as pictured below.

<figure>
<img src="https://hosting.analythium.io/content/images/2021/08/SPNginx.png" class="kg-image" sizes="(min-width: 720px) 720px" srcset="https://hosting.analythium.io/content/images/size/w600/2021/08/SPNginx.png 600w, https://hosting.analythium.io/content/images/size/w1000/2021/08/SPNginx.png 1000w, https://hosting.analythium.io/content/images/2021/08/SPNginx.png 1366w" width="1366" height="768" alt="ShinyProxy setup with Nginx." /><figcaption aria-hidden="true">ShinyProxy setup with Nginx.</figcaption>
</figure>

## Prerequisites

Start a Ubuntu 20.04 virtual machine and follow instructions from the [introductory ShinyProxy post](https://hosting.analythium.io/how-to-set-up-shinyproxy-to-host-shiny-apps/) to have the server available on `http://$HOST:8080` with the two demo applications.

If you are short on time and need a virtual machine with ShinyProxy already installed, try the [ShinyProxy 1-click app from the Digitalocean Marketplace](https://marketplace.digitalocean.com/apps/shinyproxy?refcode=a8041699739d) for $5 a month. I explain the setup in [this short video](https://youtu.be/aoIlaOYRpQs). The 1-click machine image has Nginx configured, so feel free to jump ahead to the [*Firewall settings*](#firewall-settings) section.

## Run ShinyProxy on port 80

Your ShinyProxy instance is running on port 8080 (`http://$HOST:8080`):

<figure>
<img src="https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-13-at-11.51.00-PM.png" class="kg-image" sizes="(min-width: 720px) 720px" srcset="https://hosting.analythium.io/content/images/size/w600/2021/08/Screen-Shot-2021-08-13-at-11.51.00-PM.png 600w, https://hosting.analythium.io/content/images/size/w1000/2021/08/Screen-Shot-2021-08-13-at-11.51.00-PM.png 1000w, https://hosting.analythium.io/content/images/size/w1600/2021/08/Screen-Shot-2021-08-13-at-11.51.00-PM.png 1600w, https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-13-at-11.51.00-PM.png 1808w" width="1808" height="1394" />
</figure>

This is common practice in development mode. 8080 is an alternative to port 80 (HTTP). When you type `$HOST` in the browser, it is going to take you to `http://$HOST:80` implicitly. Unlike port 80, this 8080 needs an explicit port override to request in the browser.

Port 8080 is there to remind you of the fact that you need to decide how you want to host ShinyProxy. I explain how to use Nginx as a reverse proxy to serve ShinyProxy on port 80.

### Install Nginx

[Nginx](https://nginx.org/en/) (pronounced "engine X") is a web server that can also be used as a [reverse proxy or load balancer](https://en.wikipedia.org/wiki/Nginx#HTTP_proxy_and_Web_server_features). A large portion of the Internet is running on Nginx, so it is a good idea to get some exposure. Let's install Nginx:

``` bash
apt-get install nginx
```

### Set up a reverse proxy

A reverse proxy is a server that is placed in front of other servers and forwards browser requests to those servers. In our case, Nginx forwards the request made to port 80 to ShinyProxy listening on port 8080.

You can read more about the difference between a [(forward) proxy and a reverse proxy setup here](https://www.cloudflare.com/en-ca/learning/cdn/glossary/reverse-proxy/). For our purposes, the important security consideration is that Nginx as a reverse proxy ensures that no client ever communicates directly with the ShinyProxy server.

Edit the config file (`/etc/nginx/sites-enabled/default`) with `vim` or `nano` to have the following content:

``` bash
server {
        listen 80 default_server;
        listen [::]:80 default_server;

    ## Add here your custom domain
    # server_name example.com www.example.com;
    server_name _;

    location / {
        proxy_pass          http://127.0.0.1:8080/;

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 600s;

            proxy_redirect    off;
            proxy_set_header  Host             $http_host;
            proxy_set_header  X-Real-IP        $remote_addr;
            proxy_set_header  X-Forwarded-For  $proxy_add_x_forwarded_for;
            proxy_set_header  X-Forwarded-Protocol $scheme;
        }
}
```

Save the file and restart Nginx with `service nginx restart`. After this, Nginx will send all traffic to ShinyProxy which is running on port 8080. You can visit `http://$HOST/` to access ShinyProxy:

<figure>
<img src="https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-13-at-11.55.48-PM.png" class="kg-image" sizes="(min-width: 720px) 720px" srcset="https://hosting.analythium.io/content/images/size/w600/2021/08/Screen-Shot-2021-08-13-at-11.55.48-PM.png 600w, https://hosting.analythium.io/content/images/size/w1000/2021/08/Screen-Shot-2021-08-13-at-11.55.48-PM.png 1000w, https://hosting.analythium.io/content/images/size/w1600/2021/08/Screen-Shot-2021-08-13-at-11.55.48-PM.png 1600w, https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-13-at-11.55.48-PM.png 1808w" width="1808" height="1394" />
</figure>

## Firewall settings

We will use a firewall to filter all the ports except for the SSH (22), HTTP (80) and HTTPS (443) ports.

The server behind the firewall will use other ports. For example, ShinyProxy itself will connect to the ports on the Docker host that are mapped to the individual containers. It is not recommended to make these ports accessible to the outside world.

For this reason, we install `ufw`, the [Uncomplicated Firewall](https://www.digitalocean.com/community/tutorials/how-to-set-up-a-firewall-with-ufw-on-ubuntu-20-04), to allow incoming traffic only on the above-mentioned ports:

``` bash
apt install ufw

ufw default deny incoming
ufw default allow outgoing

ufw allow ssh
ufw allow http
ufw allow https

ufw enable # answer y
```

Now check `ufw status`:

``` bash
Status: active

To                         Action      From
--                         ------      ----
22/tcp                     ALLOW       Anywhere
80/tcp                     ALLOW       Anywhere
443/tcp                    ALLOW       Anywhere
22/tcp (v6)                ALLOW       Anywhere (v6)
80/tcp (v6)                ALLOW       Anywhere (v6)
443/tcp (v6)               ALLOW       Anywhere (v6)
```

## Configure your custom domain

For a [Let's Encrypt certificate](https://letsencrypt.org/), you need a fully registered domain name. I use the `example.com` domain. You have to substitute your domain name. If you want your server hosted on an [apex domain name](https://stackoverflow.com/questions/55461299/what-is-the-correct-term-for-apex-naked-bare-root-domain-names) (`example.com`), it is recommended to set up a `www` subdomain as well. Both of the following [domain name system](https://en.wikipedia.org/wiki/Domain_Name_System) (DNS) records need to be set up for your server:

-   an `A` record with `example.com` pointing to your server's public IP address,
-   an `A` record with `www.example.com` pointing to your server's public IP address.

In the `/etc/nginx/sites-available/default` file, find the line `server_name _;` and change it to `server_name example.com www.example.com;` (see comments in the snippet above).

If you are using a subdomain, e.g. `shinyproxy.example.com`, you only need a single DNS record set up and the single subdomain added to the Nginx default configuration. This is what I am doing in my example using Google Domains:

<figure>
<img src="https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-13-at-11.59.40-PM.png" class="kg-image" sizes="(min-width: 720px) 720px" srcset="https://hosting.analythium.io/content/images/size/w600/2021/08/Screen-Shot-2021-08-13-at-11.59.40-PM.png 600w, https://hosting.analythium.io/content/images/size/w1000/2021/08/Screen-Shot-2021-08-13-at-11.59.40-PM.png 1000w, https://hosting.analythium.io/content/images/size/w1600/2021/08/Screen-Shot-2021-08-13-at-11.59.40-PM.png 1600w, https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-13-at-11.59.40-PM.png 1650w" width="1650" height="538" />
</figure>

Restart Nginx to enable your changes by `systemctl restart nginx`.

Visit your domain to see the login page:

<figure>
<img src="https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-14-at-12.06.24-AM.png" class="kg-image" sizes="(min-width: 720px) 720px" srcset="https://hosting.analythium.io/content/images/size/w600/2021/08/Screen-Shot-2021-08-14-at-12.06.24-AM.png 600w, https://hosting.analythium.io/content/images/size/w1000/2021/08/Screen-Shot-2021-08-14-at-12.06.24-AM.png 1000w, https://hosting.analythium.io/content/images/size/w1600/2021/08/Screen-Shot-2021-08-14-at-12.06.24-AM.png 1600w, https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-14-at-12.06.24-AM.png 1808w" width="1808" height="1394" />
</figure>

## Set up HTTPS

The image above showed the lock icon crossed which indicates we are accessing the custom domain over a non-secure HTTP connection. To have a secure connection over HTTPS, we need to set up [Transport Layer Security](https://en.wikipedia.org/wiki/Transport_Layer_Security) (TLS).

TLS is a protocol that encrypts traffic between the client and the server. It relies on a set of trusted third-party Certificate Authorities (CA) to establish the authenticity of certificates. One such non-profit Certificate Authority is called [Let's Encrypt](https://letsencrypt.org/), which is what we used in the example.

### Install Certbot

Let’s Encrypt certificates expire after 90 days, so it is a good idea to use software tools for auto-renewal. We use Certbot to keep certificates up-to-date.

Add repository for up to date Certbot version:

``` bash
add-apt-repository ppa:certbot/certbot
```

You'll need to press ENTER to accept.

Install Certbot's Nginx package with apt:

``` bash
apt install python3-certbot-nginx
```

Certbot is now ready to use.

### Obtaining a TLS certificate

Run this command after changing the domain names:

``` bash
certbot --nginx -d example.com -d www.example.com
```

Be careful with capitalization. Browsers might not be case sensitive but Nginx and Certbot want things nice and clean and matching DNS settings.

If this is your first time running Certbot, you will be prompted to enter an email address and agree to the terms of service. Providing a valid email address is encouraged so that you can get notified about expiring certificates or any other issues that might arise.

After doing so, Certbot will communicate with the Let's Encrypt server, then run a challenge to verify that you control the domain that you are requesting a certificate for.

When prompted whether or not to redirect HTTP traffic to HTTPS (option 1) or remove HTTP access (option 2), I usually pick the second option. This is why there is no need to firewall off port 80, see <https://letsencrypt.org/docs/allow-port-80/> for an explanation.

If you pick option 1, make sure to read the ShinyProxy security documentation and [forward request headers](https://www.shinyproxy.io/documentation/security/#forward-headers).

### Verifying auto-renewal

Run this command for a dry run: `sudo certbot renew --dry-run`.

If you reload the custom domain in your browser, it should redirect to the HTTPS, notice the lock icon is not crossed anymore:

<figure>
<img src="https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-14-at-12.24.41-AM.png" class="kg-image" sizes="(min-width: 720px) 720px" srcset="https://hosting.analythium.io/content/images/size/w600/2021/08/Screen-Shot-2021-08-14-at-12.24.41-AM.png 600w, https://hosting.analythium.io/content/images/size/w1000/2021/08/Screen-Shot-2021-08-14-at-12.24.41-AM.png 1000w, https://hosting.analythium.io/content/images/size/w1600/2021/08/Screen-Shot-2021-08-14-at-12.24.41-AM.png 1600w, https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-14-at-12.24.41-AM.png 1808w" width="1808" height="1394" />
</figure>

Remember that it is your job to follow [Docker best practices](https://hosting.analythium.io/best-practices-for-r-with-docker/) and not use admin privileges inside the containers. You should also be careful that your app is not exposing sensitive information.

If you want to stop and destroy your virtual server, do not forget to remove the DNS record for your custom domain and subdomain to prevent a [hostile subdomain takeover](https://reposify.com/blog/how-to-avoid-hostile-subdomain-takeovers/).

## Conclusions

You are in full control when it comes to ShinyProxy. You can do whatever you like, but you also need to understand how to configure your server and be willing to expand your knowledge base as needed.

Well done! You just learned how to add your custom domain to the ShinyProxy server. This will certainly help your personal or corporate branding.

But your users want also their data to be safe. That's why HTTPS is so important. Security measures for your site can always be improved, and security should always be top of your mind starting from your Shiny app to your firewall settings.

## Further reading

-   [ShinyProxy documentation: security](https://www.shinyproxy.io/documentation/security/)
-   [ShinyProxy configuration examples](https://github.com/openanalytics/shinyproxy-config-examples)
-   [Nginx documentation](http://nginx.org/en/docs/)
-   [Nginx example configurations](https://www.nginx.com/resources/wiki/start/topics/examples/full/)
-   [Should you use HTTPS?](https://doesmysiteneedhttps.com/)