Skip to content

Deploy Rust web application with NGINX and Let's Encrypt

In this article, we will guide you to set up an Axum web application in a production machine with NGINX and SSL support using Let's Encrypt. We are using Debian 10 in this tutorial. Don't worry, we applied the same steps to the Rocket web framework running on CentOS 7 and it works well.

Setting up NGINX

Install and start the NGINX.

debian@remote:~$ # for Debian
debian@remote:~$ sudo apt install -y nginx

centos@remote:~$ # for CentOS
centos@remote:~$ sudo dnf install -y nginx

debian@remote:~$ sudo systemctl enable nginx
debian@remote:~$ sudo systemctl start nginx
NGINX welcome message

If that doesn't work, you need to allow both 80 (HTTP) and 443 (HTTPS) ports using either firewall-cmd or ufw. The HTTPS port will be used later during the Let's Encrypt setup.

Setting Up Server Blocks (Optional)

Now let's create a sample server block.

debian@remote:~$ sudo mkdir -p /var/www/
debian@remote:~$ sudo chown -R $USER:$USER /var/www/

Place the code below in /var/www/

<title>Welcome to</title>
<h1>Success! Your Nginx server is successfully configured for <em></em>. </h1>
<p>This is a sample page.</p>

Then add the appropriate configuration in /etc/nginx/conf.d/

server {
listen 80;
listen [::]:80;

root /var/www/;
index index.html index.htm index.nginx-debian.html;


location / {
try_files $uri $uri/ =404;

Run sudo nginx -t to check if the configurations are OK. If no errors occurred, restart the NGINX using sudo systemctl restart nginx.

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

Warning: If you head to right now, you will get "403 Forbidden". In the case of CentOS, you need to run the code below

centos@remote:~$ sudo chcon -vR system_u:object_r:httpd_sys_content_t:s0 /var/www/

Now you will see "Success! Your NGINX server is successfully configured for".

Setting Up The Application

Prepare the Rust compiler.

debian@remote:~$ # Debian
debian@remote:~$ sudo apt -y install gcc && curl -sSf | sh -s -- -y

centos@remote:~$ # CentOS
centos@remote:~$ sudo dnf -y install gcc && curl -sSf | sh -s -- -y

debian@remote:~$ source $HOME/.cargo/env

If you get curl: command not found. You need to install it.

debian@remote:~$ # Debian
debian@remote:~$ sudo apt -y install curl

centos@remote:~$ # CentOS
centos@remote:~$ sudo dnf -y install curl

Create a directory for the application.

debian@remote:~$ sudo mkdir -p /opt/webapp && sudo chown -R $USER: /opt/webapp

Upload your source code to the remote machine.

debian@local:~$ # In local machine! Notice the username in the prompt.
debian@local:~$ cd /code/webapp
debian@local:~$ rsync -avrzP --exclude 'target/' -e "ssh -i ~vm-key.pem" . centos@

If you encounter a similar error below. Make sure rsync is installed on your remote machine.

bash: rsync: command not found
rsync: connection unexpectedly closed (0 bytes received so far) [sender]
rsync error: error in rsync protocol data stream (code 12) at io.c(228) [sender=3.2.3]

Build the application.

debian@remote:~$ cd /opt/webapp
debian@remote:~$ cargo build --release

debian@remote:~$ # export the environment variable if you had one
debian@remote:~$ source .env

Run the application ~/opt/webapp/target/release/webapp &. Make sure it runs well by hitting its endpoint in your remote machine.

debian@remote:~$  curl

Make every request to NGINX forwarded to our web application.


location / {
- try_files $uri $uri/ =404;
+ proxy_pass;

Make sure everything is OK with sudo nginx -t and restart the NGINX. Check if your web application runs well by doing the request to port 80. You can omit the port, as it is the default.

debian@remote:~$ curl
debian@remote:~$ curl

If this doesn't work, comment out this line in /etc/nginx/nginx.conf

        include /etc/nginx/conf.d/*.conf;
+ #include /etc/nginx/sites-enabled/*;

Now try to do a request from a local machine, to make sure it works from an external request. Ignore the jq command, it is just there to prettify the JSON output.

debian@remote:~$ curl --location | jq
"data": {
"status": "Running"

Stop the running application using kill or killall. Now we will use the systemd unit to manage the application. This gives us better control and persistent logging.

Put the code below in /etc/systemd/system/ Change the ExecStart to your binary location.

Warning: Avoid putting comments in systemd unit file, as it causes unexpected behavior.




If you are using CentOS, change User and Group to 'centos' (without the quotes). Maximize the use of ROCKET_* If you are using Rocket.


Now you can use systemctl command to manage the application and journalctl to see the logs.

debian@remote:~$ sudo systemctl start
debian@remote:~$ sudo systemctl status
● - webapp
Loaded: loaded (/etc/systemd/system/; disabled; vendor preset: enabled)
Active: active (running) since Thu 2022-08-18 01:57:37 UTC; 1s ago

debian@remote:~$ journalctl -u
Aug 18 01:57:37 user systemd[1]: Started webapp.
Aug 18 01:57:37 user webapp[31351]: listening on

Make sure it works the same as manual invocation.

debian@local:~$ curl
debian@local:~$ curl

Tips: Sometimes you get unexpected behavior. Don't panic!. You can always inspect the log.

If you get "Bad Gateway" from the curl result. Always consult the NGINX log.

debian@remote:~$ sudo tail -F /var/log/nginx/error.log

In my case, I got "Permission denied".

2021/05/26 00:06:11 [crit] 21856#0: *1 connect() to failed (13: Permission denied) while connecting to upstream, client:, server:, request: "GET /health HTTP/1.1", upstream: "", host: "webapp.Turns"

The issue is related to SELinux (CentOS) as described in this answer

centos@remote:~$ sudo setsebool -P httpd_can_network_connect 1

Setting Up The SSL

Install the certbot.

debian@remote:~$ sudo dnf install -y epel-release
debian@remote:~$ sudo dnf install -y certbot python3-certbot-nginx

Run the certbot.

debian@remote:~$ sudo certbot --nginx -d -d

Test if the SSL works.

debian@remote:~$ curl # {"data":{"status":"running"}}

Renew SSL Certificate Automatically

It will be a huge pain if we renew the certificate manually all the time. Let the cron handle this.

Add the code below to cron using sudo crontab -e.

0 0,12 * * * python3 -c 'import random; import time; time.sleep(random.random() * 3600)' && certbot renew --quiet
debian@remote:~$ sudo crontab -e # then paste the code above

Check if the task was inserted successfully.

debian@remote:~$ sudo crontab -l
0 0,12 * * * python3 -c 'import random; import time; time.sleep(random.random() * 3600)' && certbot renew --quiet

The code above will run in the afternoon and at midnight every day. The random section will select a random minute within the hour for the renewal.


Forwarding the header from NGINX to the application is different in Axum and Rocket. In Axum, I can get the header out of the box. But in Rocket, I need to add some solutions. I use the following configurations in my Rocket application.

    location / {
+ 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-Proto $scheme;
+ proxy_set_header X-NginX-Proxy true;
+ proxy_redirect off;

The initial article is using Rocket and was written a year ago. The improved version is using Axum and is written today. So take the time difference into the account too.

If you liked this article, please support my work. It will definitely be rewarding and motivating. Thanks for the support!