How to create & configure a NGINX server on DigitalOcean

How to create & configure a NGINX server on DigitalOcean

We may earn an affiliate commission through purchases made from our guides and tutorials.

Here’s how I’d set up a clean, fast NGINX server on DigitalOcean, the way you’d do it if you actually planned to run it for years without babysitting.

You’ll need a DigitalOcean account. (obvious, but people forget.) Once you’ve got that, we’ll spin up a Droplet (DigitalOcean’s VM), lock it down, install NGINX, and lay down sane defaults: separate server blocks, proper logs, a firewall that actually blocks things, and a few best-practice tweaks you’ll thank yourself for later.

NGINX started life as a high-performance web server and reverse proxy built to handle huge concurrency with low memory. It’s event-driven, stable, and powers a lot of the web—perfect as your primary web server or a front door in front of app servers. We’ll use it as a straightforward HTTP(S) server with room to grow into a reverse proxy later.

Create the Droplet

You can do this two ways: in the Control Panel (clicky-click) or with doctl (DigitalOcean’s CLI). If you like repeatable infra, use the CLI.

Control Panel
Pick the latest Ubuntu LTS image, add your SSH key, enable Monitoring, and create the Droplet. (Monitoring is one checkbox during creation.)

CLI with doctl (after you’ve installed and authenticated it):

doctl compute droplet create web-1 \
  --region fra1 \
  --size s-1vcpu-1gb \
  --image ubuntu-24-04-x64 \
  --ssh-keys <your_key_fingerprint> \
  --enable-ipv6 \
  --tag-names web,nginx,production

This is the boring, reliable default: small VM, Ubuntu LTS, IPv6 on, tags added for future firewalling and management. Command flags and images are documented here if you want a different size or region.

If you skipped Monitoring on creation, you can add DigitalOcean’s metrics agent later:

wget -qO- https://repos-droplet.digitalocean.com/install.sh | sudo bash

That wires metrics into the DO dashboard (CPU, load, disk, etc.).

First login + basic hardening

SSH in as the user DigitalOcean gave you (or root if you used a password). Do the usual housekeeping:

sudo apt update
sudo apt upgrade -y
sudo apt install -y ufw

Create a non-root user with sudo and key-based auth if you don’t already have one; then disable password SSH logins. DigitalOcean’s “Initial Server Setup” is the canonical checklist if you want to go line-by-line.

Firewall: two layers, not one

Use both UFW and DigitalOcean Cloud Firewalls. UFW protects the box locally; DO’s firewall stops junk before it ever hits your NIC. They’re complementary.

On the server (UFW):

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow http
sudo ufw allow https
sudo ufw enable
sudo ufw status verbose

That’s the crisp baseline: SSH, 80, 443 only. If you run nonstandard ports later, add rules consciously, not out of habit.

In the DO Control Panel (Cloud Firewall): create a firewall that allows 22/80/443 from 0.0.0.0/0 and ::/0, then assign it to your Droplet(s). You can also manage this with doctl if you prefer.

Install NGINX the straightforward way

Ubuntu’s repository NGINX is fine for most use cases.

sudo apt install -y nginx
sudo systemctl enable --now nginx
sudo systemctl status nginx --no-pager

DigitalOcean’s guide lines up with this: install, open the firewall, check the service, and confirm the default page shows up on your Droplet’s IP.

Create proper server blocks (one site, one file)

Don’t cram everything into the default server. Create a new server block per domain and leave the default as a safety net.

sudo mkdir -p /var/www/example.com/html
sudo chown -R $USER:$USER /var/www/example.com

Now write the server block:

sudo nano /etc/nginx/sites-available/example.com

Paste this minimal, clean config:

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

    server_name example.com www.example.com;

    root /var/www/example.com/html;
    index index.html;

    access_log /var/log/nginx/example.com.access.log main;
    error_log  /var/log/nginx/example.com.error.log warn;

    # Basic security headers (tweak as needed)
    add_header X-Content-Type-Options nosniff;
    add_header X-Frame-Options SAMEORIGIN;
    add_header Referrer-Policy no-referrer-when-downgrade;

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

Enable it safely:

sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

NGINX’s own docs explain the hierarchy (http > server > location) and why separate server blocks keep things sane. For deeper matching rules later (exact vs prefix vs regex), study location selection—worth your time.

Add a quick test page:

echo '<h1>It works.</h1>' | tee /var/www/example.com/html/index.html

(Optional but smart) HTTPS with Let’s Encrypt

If your DNS is pointed at the Droplet, HTTPS is a two-liner with Certbot:

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com

It’ll add the TLS server block, set up auto-renewal, and (if you say yes) redirect HTTP→HTTPS for you. Let’s Encrypt + NGINX on Ubuntu is well-documented and battle-tested.

Reverse proxying to an app (when you need it)

When you eventually run a backend app on localhost:3000 (or whatever), swap your location / for this:

location / {
    proxy_pass         http://127.0.0.1:3000;
    proxy_http_version 1.1;
    proxy_set_header   Host $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_read_timeout 60s;
}

Keep static assets on NGINX where possible; let the app do app things. The location matching guides will help you add special cases (e.g., /api/ vs /assets/).

Logging that won’t fill your disk

By default, Ubuntu drops an NGINX logrotate policy in /etc/logrotate.d/nginx. Confirm it exists and rotates daily; tweak retention if you need more or less history.

sudo nano /etc/logrotate.d/nginx

A sane example looks like:

/var/log/nginx/*.log {
    daily
    missingok
    rotate 14
    compress
    delaycompress
    notifempty
    create 0640 www-data adm
    sharedscripts
    postrotate
        [ -f /run/nginx.pid ] && kill -USR1 $(cat /run/nginx.pid)
    endscript
}

That postrotate line tells NGINX to reopen logs, so rotation actually works without losing lines. If you want to go deeper on formats, custom log_format, and log rotation behavior, these guides are solid.

Useful NGINX knobs you’ll touch sooner or later

Open the main config to set global stuff like gzip and keepalive:

sudo nano /etc/nginx/nginx.conf

Two minimal, safe defaults to enable:

http {
    ##
    # ... existing config ...
    ##

    sendfile on;
    tcp_nopush on;
    keepalive_timeout 65;

    gzip on;
    gzip_types text/plain text/css application/javascript application/json application/xml+rss application/xml text/javascript image/svg+xml;
    gzip_min_length 1024;

    # log_format main is already defined on Ubuntu; keep it.
}

Don’t get cute with ultra-aggressive buffers or timeouts until you measure.

Make your setup maintainable

Reloads and service control:

sudo nginx -t
sudo systemctl reload nginx
sudo systemctl restart nginx
sudo journalctl -u nginx -e --no-pager

Backups & snapshots:
Take a snapshot when you hit a known-good point; it’s cheap insurance. If you’re scripting with doctl, tags and snapshot commands are your friends.

Monitoring:
If you enabled the metrics agent, you’ll see CPU, RAM, disk, and network in the DO dashboard. That’s usually enough for a single host. Upgrade or reinstall the agent from the DO docs if it ever goes stale.

Add more sites cleanly

Each new domain gets:

/var/www/<domain>/html
/etc/nginx/sites-available/<domain>
/etc/nginx/sites-enabled/<domain>  -> symlink

Copy a known-good server block, change the names, update server_name, test with nginx -t, reload, then (when DNS is in place) run Certbot again for HTTPS.

Troubleshooting like an adult

  • Port open but no site? sudo ss -tulpen | grep :80 to see who’s listening. UFW and Cloud Firewall both must allow 80/443.
  • Config fails to reload? sudo nginx -t before you touch systemctl. It prints the exact file+line.
  • Log spam / full disk? Adjust /etc/logrotate.d/nginx retention; force a rotation with sudo logrotate -f /etc/logrotate.conf.
  • Weird routing? Re-read how NGINX picks server/location—exact matches beat prefixes, and regex comes with its own precedence. It’s deterministic once you know the rules.

Where you can safely stop (and what to do later)

If you followed along, you’ve got: a Droplet with monitoring, a firewall that isn’t theater, NGINX serving a proper server block, TLS available when DNS points home, and logs that rotate. That’s a production-worthy foundation.

Things to add later—when you actually need them:

  • Brotli or advanced gzip tuning (measure first).
  • Systemd overrides for tighter resource limits.
  • Separate upstreams with health checks if you start proxying to apps.
  • Off-box logging/metrics when one host becomes five.

The point is simple: do the basics well and you won’t be firefighting at 3 a.m.

If you want me to tailor this to your exact stack (WordPress, Node, Django, whatever), say the word in the comment section below and I’ll add the right reverse-proxy snippets and hardening for that app.

Was this helpful?

Thanks for your feedback!
Alex is the resident editor and oversees all of the guides published. His past work and experience include Colorlib, Stack Diary, Hostvix, and working with a number of editorial publications. He has been wrangling code and publishing his findings about it since the early 2000s.

Leave a comment

Your email address will not be published. Required fields are marked *