A guide to hosting static websites using NGINX

Learn how to host a static website using a VM and NGINX!

NGINX is a very powerful web server. You can do a ton of things with it, such as setting up reverse proxies or load balancing. It can also be used to host your static website.

Now, keep in mind that there are many options when it comes to hosting static websites nowdays — Github pages, any number of hosting providers, Amazon S3 or Cloudfront, Cloudflare, etc. This is just one option among many.

Like this article? Add me on LinkedIn!

This guide assumes some things:

  • You’re comfortable using Linux.
  • You’re trying to host a basic static website on a VM.
  • You don’t know how to use NGINX.

Step 1: Get a server or a VM.

You’ll need shell access to follow this guide. I recommend a $5/month droplet from DigitalOcean, but it doesn’t really matter where it is.

Step 2: Point your domain name to the new server

Your domain name needs to point to your new server. Create an A record in your hosting provider’s DNS settings, pointing your domain name (eg. jgefroh.com) to the server IP address (eg. If you don’t want to wait for the DNS to propagate, edit your /etc/hosts file to point your domain to the right IP address.

For the purposes of this guide, we’ll use the domain name jgefroh.com and the IP address as examples. Switch out with your actual domain name and IP address as needed when you encounter these.

Step 3: Install NGINX

ssh into your server and use your favorite package manager to install NGINX. If using Ubuntu, you can run:

sudo apt-get update
sudo apt-get install nginx

Step 4: Move your website’s static files to the server

You can’t deliver your website if the server doesn’t have your files, so let’s add your files to the server.

By default, NGINX expects your static files to be in a specific directory (which varies). You can override this in the configuration. For now, let’s assume that you’ll be putting your website’s static files in the /var/www/ directory.

Create a directory in /var/www/ called jgefroh.com. This is where your static website’s files will go.

Copy your website’s static files into that folder. You can use the scp command from your local machine. cd into your website’s directory and run:

scp -r * root@

Be sure to replace the and jgefroh.com with values appropriate to you.

If you don’t have a website just yet, you can create a file called index.html with some “Coming soon” text as a placeholder.

Step 4: Configure NGINX to serve your website

You’ll need to tell NGINX about your website and how to serve it.

cd into /etc/nginx/. This is where the NGINX configuration files are located.

The two directories we are interested are sites-available and sites-enabled.

  • sites-available contains individual configuration files for all of your possible static websites.
  • sites-enabled contains links to the configuration files that NGINX will actually read and run.

What we’re going to do is create a configuration file in sites-available, and then create a symbolic link (a pointer) to that file in sites-enabled to actually tell NGINX to run it.

Create a file calledjgefroh.com in the sites-available directory and add the following text to it:

server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/jgefroh.com; index index.html; server_name jgefroh.com www.jgefroh.com; location / {
try_files $uri $uri/ =404;

Be sure to replace jgefroh.com with your actual domain name.

This file tells NGINX several things:

  • Deliver files from the folder /var/www/jgefroh.com
  • The main index page is called index.html.
  • Requests that are requesting jgefroh.com should be served by this server block.
  • Note the www is also listed separately. This tells nginx to also route requests starting with www to the site. There’s actually nothing special about the www — it’s treated like any other subdomain.

Now that the file is created, we’ll add it to the sites-enabled folder to tell NGINX to enable it. The syntax is as follows:


The actual syntax will look like:

ln -s /etc/nginx/sites-available/jgefroh.com /etc/nginx/sites-enabled/jgefroh.com

Now, if you were to restart NGINX you should see your site!

sudo systemctl restart nginx

If it gives you an error, there’s likely a syntax error. You can stop here if you’d like, but you can also continue for some more optimization.

Enable HTTPS

With the advent of free SSL certs from LetsEncrypt, there’s really no reason why you shouldn’t have HTTPS enabled for your website. In addition to the improved security, there’s significant performance opportunities it allows via HTTP/2 (browser vendors require encryption to enable this), you’ll increase user confidence, and you’ll even rank higher in SEO.

Step 1: Acquire an SSL cert

There’s multiple ways to do this. You can buy a single-domain certification or a wildcard certification if you plan on securing subdomains.

You can also go the free route via LetsEncrypt:

sudo apt-get install software-properties-common
sudo add-apt-repository ppa:certbot/certbot
sudo apt-get update
sudo apt-get install python-certbot-nginx
sudo certbot --nginx certonly

Follow the instructions. This will install certs in /etc/letsencrypt/live/jgefroh.com/;

Enable auto-renewal for certificates:

Edit the crontab and create a CRON job to run the renewal command:

sudo crontab -e

Add the following line:

17 7 * * * certbot renew --post-hook "systemctl reload nginx"

Step 2: Tell NGINX to use the SSL cert for your website

Once you’ve acquired your SSL certs, you’ll need to let NGINX know to use them.

Let’s modify the configuration file we created for jgefroh.com to use SSL.

Inside the server block we created, add the following text, changing the paths to point to wherever the certificate file and the key file are stored (usually store in the /etc/nginx/certs/ directory):

server {
# ...previous content here
ssl on;
ssl_certificate /etc/letsencrypt/live/jgefroh.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/jgefroh.com/privkey.pem;

This tells nginx to enable SSL and use the specified key and certificate for that server.

We also now face an issue: Port 80, what we’re currently listening to, is for HTTP connections. SSL connections use port 443. The solution? Change the port from 80 to 443.

server {
listen 443 default_server;
listen [::]:443 default_server;
#... all other content

This however, breaks people going to the website without https:// explicitly in the URL. To fix this, we’ll redirect HTTP requests to the HTTPS url. Add the following new server block after the HTTPS (443) server block:

server {
server_name jgefroh.com www.jgefroh.com;
rewrite ^ https://$host$request_uri? permanent;

This will redirect all requests to jgefroh.com and www.jgefroh.com on port 80 to the HTTPS URL.

Now, restart NGINX…

sudo systemctl restart nginx

…and you should have SSL enabled!

Test it by going to the four variations of your URL, eg.:

  • http://jgefroh.com
  • https://jgefroh.com
  • http://www.jgefroh.com
  • https://www.jgefroh.com

They should all work and be secured via HTTPS.

Improve performance

Enable HTTP/2

HTTP/2 allows browsers to request files in parallel, greatly improving the speed of delivery. You’ll need HTTPS enabled. Edit your browser configuration file, adding http2 to the listen directive, then restart NGINX:

server {
listen 443 http2 default_server;
listen [::]:443 http2 default_server;
#... all other content

Enable gzip compression

gzip compression can greatly decrease the size of files during transmission (sometimes by over 80%).

Add the following to your server block:

server {
#...previous content
gzip on;
gzip_types application/javascript image/* text/css;
gunzip on;

This will ensure that javascript files, images, and CSS files are always compressed.


A security vulnerability exists when you enable gzip compression in conjunction with HTTPS that allows attackers to decrypt data. For static websites that don’t serve users sensitive data, this is less of an issue, but for any site serving sensitive information you should disable compression for those resources.

Enable client-side caching

Some files don’t ever change, or change rarely, so there’s no need to have users re-download the latest version. You can set cache control headers to provide hints to browsers to let them know what files they shouldn’t request again.

server {
#...after the location / block
location ~* \.(jpg|jpeg|png|gif|ico)$ {
expires 30d;
location ~* \.(css|js)$ {
expires 7d;

Examine how frequently your various file types change, and then set them to expire at appropriate times. If .css and .js files change regularly, you should set the expiration to be shorter. If image files like .jpg never change, you can set them to expire months from now.

Dynamically route subdomains to folders

If you have subdomains, chances are you don’t want to have to route every subdomain to the right folder. It’s a maintenance pain. Instead, create a wildcard server block for it, routing to the folder that matches the name:

server {
server_name ~^(www\.)(?<subdomain>.+).jgefroh.com$ ;
root /var/www/jgefroh.com/$subdomain;
server {
server_name ~^(?<subdomain>.+).jgefroh.com$ ;
root /var/www/jgefroh.com/$subdomain;

Restart nginx, and you’ll automatically route subdomains to the same-named subfolder.

Did you find this story helpful? Please Clap to show your support!
If you didn’t find it helpful, please let me know why with a

Joseph Gefroh is currently the Director of Engineering at Snap! Raise, a leading Seattle fintech startup. He has nearly a decade of software engineering and management experience, and has led engineering organizations through hyper-growth, acquisitions, and other challenges.

Director of Engineering @ HealthSherpa. Opinions my own. Add me on LinkedIn! https://www.linkedin.com/in/jgefroh/

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store