How To Install Nginx on Ubuntu

Install Nginx on Ubuntu

If you have been running a web project on Ubuntu and hitting performance walls, the web server you choose is often the first thing worth looking at. Nginx (pronounced “engine-x”) is a high-performance, open-source web server that powers over 33% of all active websites globally, including Netflix, Cloudflare, and Pinterest. Unlike older web server models that spin up a new process for each incoming connection, Nginx uses an event-driven, non-blocking architecture that lets a single worker process handle thousands of simultaneous connections without breaking a sweat on RAM.

This guide shows you exactly how to install Nginx on Ubuntu 22.04, 24.04, or 25.04 from scratch, including firewall configuration, server blocks, performance tuning, SSL setup, and security hardening. Every step includes a plain explanation of not just what to run, but why you are running it. That makes the difference between someone who copies commands and someone who actually understands their server.

By the end of this Linux server tutorial, you will have a production-ready Nginx web server running on Ubuntu, with a secured firewall, proper directory structure, and SSL enabled via Let’s Encrypt.

What Is Nginx and Why Does It Matter on Ubuntu?

Nginx started as a solution to the C10K problem: how do you handle 10,000 concurrent connections on a single server? The answer was to ditch the one-thread-per-connection model and replace it with an async, event-loop architecture.

Ubuntu is the go-to Linux distribution for web servers for a reason. Its APT package manager handles dependency resolution automatically, Ubuntu LTS releases come with five years of security patches, and the ecosystem of documentation around Ubuntu and Nginx is the richest in the Linux world.

Here is a quick comparison between Nginx and Apache so you understand why this guide exists:

Feature Nginx Apache
Connection model Async, event-driven Process/thread per connection
Memory under load Low and stable Increases with connections
Static file performance Excellent Good
Config complexity Moderate Moderate
.htaccess support No (by design) Yes
Reverse proxy Built-in Requires mod_proxy

The bottom line: if you are serving static assets, running a reverse proxy for Node.js or Python apps, or expecting traffic spikes, Nginx on Ubuntu is the right combination.

Prerequisites

Before you run a single command, make sure your environment meets these requirements:

  • Ubuntu 22.04 LTS, 24.04 LTS, or 25.04 — this guide is tested on all three
  • A non-root sudo user account — running everything as root is a security liability; a sudo user gives you controlled privilege escalation
  • SSH access or direct terminal access to the server
  • UFW firewall available — it ships with Ubuntu by default
  • An active internet connection on the server so APT can reach Ubuntu’s repositories
  • Optional: a registered domain name if you plan to configure server blocks and SSL

Ubuntu Version and Nginx Package Compatibility

Different Ubuntu versions ship different Nginx versions from their default APT repositories. Knowing this upfront prevents surprises after installation.

Ubuntu Version Default Nginx Version LTS Support Until
Ubuntu 22.04 LTS 1.18.0+ April 2027
Ubuntu 24.04 LTS 1.24.0+ April 2029
Ubuntu 25.04 1.26.0+ January 2026

If you need the absolute latest mainline Nginx features, you can add the Ondrej PPA:

sudo add-apt-repository ppa:ondrej/nginx
sudo apt update

For most production use cases, the version from Ubuntu’s official repositories is stable, well-tested, and sufficient.

Step 1: Update Your System Package Index

Why this matters: The APT package index is a local cache of what software versions are available in Ubuntu’s repositories. If this cache is stale, you risk installing an outdated version of Nginx or hitting dependency mismatches. Running apt upgrade first also patches any known OS vulnerabilities before you expose the server to internet traffic.

Think of it this way: you would not install a new door lock on a house with broken windows. Patch the OS first.

sudo apt update
sudo apt upgrade -y

Expected output: APT will print a list of package sources it fetched, followed by a count of upgradeable packages. On a fresh Ubuntu install, this might upgrade 20 to 80 packages. That is normal.

After the upgrade completes, it is a good habit to reboot if the kernel was updated:

sudo reboot

Wait 30 to 60 seconds, then SSH back into your server before continuing.

Step 2: Install Nginx on Ubuntu Using APT

Installing the Package

Why use APT instead of compiling from source? APT manages all dependencies automatically, integrates Nginx into systemd for service management, and ensures that future apt upgrade runs will apply security patches to Nginx without any manual work. Compiling from source gives you more compile-time options but forces you to manage all of that yourself. For production servers, APT wins.

sudo apt install nginx -y

APT pulls nginx-core, nginx-common, and all required dependencies in one shot.

Verify the Installation

nginx -v

Expected output:

nginx version: nginx/1.18.0 (Ubuntu)

The exact version number depends on your Ubuntu release, but you should always verify this. A missing or incorrect version means the package did not install properly.

You can also confirm the full package installation with:

dpkg -l | grep nginx

This lists every Nginx-related package installed on the system. You should see entries for nginx, nginx-core, and nginx-common at minimum.

Step 3: Configure the UFW Firewall to Allow Web Traffic

Why you cannot skip this step: Ubuntu’s UFW firewall blocks all inbound connections by default. Your Nginx installation is running, but no browser in the world can reach it until you open the right ports. Port 80 handles standard HTTP traffic. Port 443 handles HTTPS. Both need to be explicitly allowed.

Nginx registers itself with UFW during installation and creates three application profiles:

  • Nginx HTTP — opens port 80 only
  • Nginx HTTPS — opens port 443 only
  • Nginx Full — opens both ports 80 and 443

View the available profiles:

sudo ufw app list

Allow HTTP Traffic First

At this stage you do not have an SSL certificate yet, so start with HTTP only:

sudo ufw allow 'Nginx HTTP'

Critical: also make sure OpenSSH is allowed before you enable UFW, or you will lock yourself out of the server completely:

sudo ufw allow OpenSSH
sudo ufw enable
sudo ufw status

Expected output:

Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere
Nginx HTTP                 ALLOW       Anywhere

Later, after you set up SSL certificates, you will switch Nginx HTTP to Nginx Full:

sudo ufw delete allow 'Nginx HTTP'
sudo ufw allow 'Nginx Full'

Step 4: Verify That Nginx Is Running on Ubuntu

Check the systemd Service Status

Why check systemd and not just ping the IP? Ubuntu 22.04+ uses systemd as its init system. systemctl status gives you real-time information: whether the master process is active, its PID, memory usage, the last few log lines, and whether it is set to start on boot. A quick IP ping only tells you if the server is reachable — it tells you nothing about whether Nginx is actually serving requests.

sudo systemctl status nginx

Expected output:

● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
     Active: active (running) since Tue 2026-05-05 09:00:00 UTC; 2min ago

If the status shows active (running) in green, Nginx is live.

Confirm It Is Reachable From a Browser

Get your server’s public IP address:

curl -4 icanhazip.com

Then open a browser and navigate to:

http://your_server_ip

You should see the default Nginx welcome page that reads “Welcome to nginx!” This confirms the web server is installed, running, and accessible through the firewall.

Step 5: Manage the Nginx Service with systemctl

Understanding service management is not optional for anyone running Nginx on a production server. You will use these commands regularly, and knowing when to use reload versus restart can mean the difference between a graceful config update and a few seconds of downtime.

Here are the core commands you need:

# Start a stopped Nginx service
sudo systemctl start nginx

# Stop the service completely (drops all active connections)
sudo systemctl stop nginx

# Restart the service (stop + start — use after major config changes)
sudo systemctl restart nginx

# Reload config without dropping active connections (preferred in production)
sudo systemctl reload nginx

# Enable Nginx to start automatically after a server reboot
sudo systemctl enable nginx

# Disable automatic startup
sudo systemctl disable nginx

Why prefer reload over restart in production? The reload command sends a SIGHUP signal to the Nginx master process, which tells it to re-read the config file and update worker processes without terminating existing connections. Users currently downloading files or submitting forms will not be interrupted. Always use reload after editing config files unless you are making changes that require a full process restart.

Always test your config before reloading:

sudo nginx -t

If the output says syntax is ok and test is successful, then reload safely.

Step 6: Configure Nginx Server Blocks on Ubuntu

Why Server Blocks Exist

By default, Nginx serves content from /var/www/html for all requests. That works for a single website. The moment you need to host two or more domains on the same server, you need server blocks — Nginx’s equivalent of Apache’s virtual hosts. Each server block maps a domain name to its own directory, config, and log files.

Create the Web Root Directory

Replace your_domain with your actual domain name throughout this section:

sudo mkdir -p /var/www/your_domain/html
sudo chown -R $USER:$USER /var/www/your_domain/html
sudo chmod -R 755 /var/www/your_domain

Why chown and chmod matter here: Nginx’s worker processes run as the www-data user. If file permissions are too restrictive, Nginx returns a 403 Forbidden error even though the files exist. The 755 permission gives the owner full access and everyone else read-execute access, which is the minimum Nginx needs to serve files.

Create a simple test page:

nano /var/www/your_domain/html/index.html

Add this basic HTML:

<html>
  <head><title>Welcome to your_domain</title></head>
  <body><h1>Server block is working.</h1></body>
</html>

Create the Server Block Configuration File

sudo nano /etc/nginx/sites-available/your_domain

Paste this configuration:

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

    root /var/www/your_domain/html;
    index index.html index.htm;

    server_name your_domain www.your_domain;

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

Why try_files $uri $uri/ =404 instead of just index? This directive tells Nginx to first look for the exact requested file, then a directory by that name, and return a 404 only if neither exists. It is cleaner than a simple index directive and handles edge cases in URL routing properly.

Enable the Server Block

sudo ln -s /etc/nginx/sites-available/your_domain /etc/nginx/sites-enabled/

Why use a symlink instead of copying the file? The sites-available directory stores all server block definitions, active or not. The sites-enabled directory contains only symlinks to configs that Nginx should actually load. To disable a site, you remove the symlink without touching the original config. It is clean, reversible, and standard practice across the Linux sysadmin community.

Test and apply the config:

sudo nginx -t
sudo systemctl reload nginx

Step 7: Nginx File and Directory Structure Explained

Editing the wrong config file is one of the most common mistakes beginners make. Here is every important path you need to know:

Path Purpose
/etc/nginx/nginx.conf Global config — controls workers, gzip, logging, and timeouts
/etc/nginx/sites-available/ Stores all server block definitions (active or not)
/etc/nginx/sites-enabled/ Contains only active symlinks that Nginx loads at startup
/etc/nginx/snippets/ Reusable config fragments (e.g., shared SSL parameters)
/var/www/html Default web root for the default server block
/var/log/nginx/access.log Every request is logged here; use for traffic analysis
/var/log/nginx/error.log All errors go here; first place to check when something breaks

Why understand the directory layout? Because a config error in /etc/nginx/nginx.conf affects every site on the server. A config error in /etc/nginx/sites-available/your_domain affects only that one site. Knowing the scope of each file prevents accidental outages.

Step 8: Security Hardening and Performance Tuning for Nginx on Ubuntu

Hide the Nginx Version Number

By default, Nginx broadcasts its exact version number in HTTP response headers. Attackers use that version string to look up known CVEs and target unpatched servers. Turn it off immediately.

Open the global config:

sudo nano /etc/nginx/nginx.conf

Inside the http {} block, add or confirm these directives:

http {
    server_tokens off;

    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "no-referrer-when-downgrade" always;

    client_max_body_size 10M;
    keepalive_timeout 15;
}

Why each header matters:

  • server_tokens off — hides the Nginx version from response headers
  • X-Frame-Options SAMEORIGIN — blocks your pages from being embedded in iframes on other domains, preventing clickjacking attacks
  • X-Content-Type-Options nosniff — stops browsers from guessing MIME types, which blocks a class of content injection attacks
  • X-XSS-Protection — activates the browser’s built-in cross-site scripting filter
  • client_max_body_size 10M — limits upload size; without this, a single large upload can exhaust disk space

Tune Worker Processes for Concurrency

The default Nginx worker configuration is conservative. For any server under real load, tune these settings in /etc/nginx/nginx.conf:

worker_processes auto;
worker_rlimit_nofile 65535;

events {
    worker_connections 4096;
    use epoll;
    multi_accept on;
}

Why worker_processes auto? It matches the number of worker processes to your server’s CPU core count automatically. Adding more workers than CPU cores creates context-switching overhead with no throughput benefit.

Why use epoll? epoll is Linux’s most efficient I/O event notification mechanism. It handles thousands of file descriptors with a single system call, dramatically reducing CPU overhead at scale compared to older methods like select or poll.

Why multi_accept on? When enabled, each Nginx worker accepts all new connections in a single pass instead of one at a time. Under burst traffic, this reduces latency and prevents connection queue buildup.

Enable Gzip Compression

Gzip reduces the size of text-based responses before sending them to the browser. This directly impacts page load speed, bandwidth costs, and Core Web Vitals scores:

http {
    gzip on;
    gzip_comp_level 5;
    gzip_min_length 256;
    gzip_vary on;
    gzip_types
        text/plain
        text/css
        text/javascript
        application/javascript
        application/json
        application/xml
        image/svg+xml;
}

Why not set gzip_comp_level to 9? Level 9 gives the best compression ratio but uses significantly more CPU per request. Level 5 hits the sweet spot: roughly 75% of maximum compression at a fraction of the CPU cost.

Step 9: Set Up SSL/TLS With Let’s Encrypt on Ubuntu

Why HTTPS is mandatory in 2026: Browsers flag HTTP sites as “Not Secure” with a visible warning in the address bar. Google confirmed in 2014 that HTTPS is a ranking signal, and since then it has become table stakes for any site that wants to rank. On top of SEO, unencrypted HTTP traffic is readable by any ISP, coffee shop router, or network middlebox between the user and your server.

Install Certbot and the Nginx plugin:

sudo apt install certbot python3-certbot-nginx -y

Obtain and auto-configure your certificate:

sudo certbot --nginx -d your_domain.com -d www.your_domain.com

Why the --nginx flag? Certbot’s Nginx plugin reads your existing server block, contacts Let’s Encrypt, downloads the signed certificate, and automatically rewrites your Nginx config to enable HTTPS and redirect HTTP to HTTPS. You do not have to touch the config manually.

Certbot sets up a systemd timer for auto-renewal. Test it:

sudo certbot renew --dry-run

If the dry run completes without errors, your certificates will renew automatically before they expire. Let’s Encrypt certificates last 90 days. Certbot renews them at the 60-day mark.

After enabling HTTPS, update UFW to allow both ports:

sudo ufw delete allow 'Nginx HTTP'
sudo ufw allow 'Nginx Full'

Troubleshooting Common Nginx Errors on Ubuntu

Even with a clean setup, you will hit issues. Here are the five most common ones and how to fix them fast.

1. Nginx Fails to Start After a Config Change

Symptom: sudo systemctl restart nginx returns an error.

Root cause: A typo or syntax error in a config file.

Fix:

sudo nginx -t

This command pinpoints the exact file and line number with the error. Fix the issue, then restart Nginx. Never restart without running nginx -t first.

2. 403 Forbidden Error

Symptom: Browser shows 403 Forbidden when accessing your site.

Root cause: Nginx cannot read the files in your web root because of wrong permissions or ownership.

Fix:

sudo chmod -R 755 /var/www/your_domain
sudo chown -R www-data:www-data /var/www/your_domain

Also verify that the root directive in your server block points to the correct directory.

3. Site Not Loading After Server Block Configuration

Symptom: Browser times out or shows “This site can’t be reached.”

Root cause: UFW is still blocking port 80.

Fix:

sudo ufw status
sudo ufw allow 'Nginx HTTP'
sudo ufw enable

Confirm UFW shows Nginx HTTP as ALLOW in the output.

4. Config Changes Are Not Taking Effect

Symptom: You edited a config file but the site behavior has not changed.

Root cause: Nginx was not reloaded after the edit.

Fix:

sudo nginx -t
sudo systemctl reload nginx

Always test before reloading. A broken config that gets reloaded will crash the Nginx process.

5. Port 80 Already in Use

Symptom: nginx -t passes but Nginx fails to bind to port 80 on start.

Root cause: Apache or another service is already listening on port 80.

Fix:

sudo ss -tlnp | grep :80
sudo systemctl stop apache2
sudo systemctl start nginx

If you do not need Apache at all, disable it permanently:

sudo systemctl disable apache2
r00t is a Linux Systems Administrator and open-source advocate with over ten years of hands-on experience in server infrastructure, system hardening, and performance tuning. Having worked across distributions such as Debian, Arch, RHEL, and Ubuntu, he brings real-world depth to every article published on this blog. r00t writes to bridge the gap between complex sysadmin concepts and practical, everyday application — whether you are configuring your first server or optimizing a production environment. Based in New York, US, he is a firm believer that knowledge, like open-source software, is best when shared freely.

Related Posts