$linuxjunkies
>

How to Host a Website on a Linux Server

From a fresh VPS to a live HTTPS site: configure DNS, install Nginx, set up a virtual host, and issue a free Let's Encrypt TLS certificate with Certbot.

BeginnerUbuntuDebianFedoraArch8 min readUpdated June 7, 2026

Before you start

  • A VPS with a public IP address and SSH access
  • A registered domain name with access to its DNS settings
  • A non-root user with sudo privileges on the server

A fresh VPS, a domain name, and about an hour is all you need to serve a real website over HTTPS. This guide walks through every layer: DNS, web server installation, virtual host configuration, and a free TLS certificate via Let's Encrypt. Commands are shown for Debian/Ubuntu, Fedora/RHEL, and Arch where they differ.

Step 1: Point Your Domain to the Server

Before touching the server, log into your domain registrar and create two DNS records. Replace 203.0.113.10 with your actual VPS IP and example.com with your domain.

  • A record: example.com203.0.113.10
  • A record: www.example.com203.0.113.10

DNS propagation takes anywhere from a few minutes to 48 hours. You can check progress with:

dig +short example.com A
dig +short www.example.com A

Both should return your server IP before you attempt TLS certificate issuance. If you try to get a certificate before DNS resolves, Certbot will fail.

Step 2: Secure the Server First

Run updates and make sure SSH key authentication is your only login method before opening web ports.

Update packages

# Debian / Ubuntu
sudo apt update && sudo apt upgrade -y

# Fedora / RHEL / Rocky
sudo dnf upgrade -y

# Arch
sudo pacman -Syu

Open firewall ports

You need ports 80 (HTTP) and 443 (HTTPS) open. Port 80 is required even if you plan to redirect everything to HTTPS — Certbot's HTTP-01 challenge uses it.

# UFW (Debian/Ubuntu default)
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status
# firewalld (Fedora/RHEL/Rocky)
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
# nftables (Arch or manual setup)
# Add to /etc/nftables.conf inside your input chain:
# tcp dport { 80, 443 } accept
sudo systemctl restart nftables

Step 3: Install Nginx

Nginx is a solid default for new deployments — low memory footprint, straightforward config syntax, and excellent static-file performance. Apache is equally valid; the concepts are the same.

# Debian / Ubuntu
sudo apt install nginx -y

# Fedora / RHEL / Rocky
sudo dnf install nginx -y

# Arch
sudo pacman -S nginx

Enable and start the service with systemd:

sudo systemctl enable --now nginx

Verify it's running:

sudo systemctl status nginx

The output should show active (running). At this point, visiting your server's IP in a browser should show the default Nginx welcome page.

Step 4: Create a Virtual Host (Server Block)

A virtual host tells Nginx which directory to serve for a given domain. The config layout differs slightly between distros.

Create the web root and a test page

sudo mkdir -p /var/www/example.com/html
sudo chown -R $USER:$USER /var/www/example.com/html
echo '<h1>It works</h1>' > /var/www/example.com/html/index.html

Write the server block config

# Debian/Ubuntu path:
sudo nano /etc/nginx/sites-available/example.com

# Fedora/RHEL/Arch path (drop directly in conf.d):
sudo nano /etc/nginx/conf.d/example.com.conf

Paste the following, substituting your domain:

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

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

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

Enable the config (Debian/Ubuntu only)

Debian/Ubuntu uses a sites-enabled symlink pattern. Fedora/RHEL/Arch load everything in conf.d/ automatically.

sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default

Test and reload

sudo nginx -t
sudo systemctl reload nginx

nginx -t must report syntax is ok and test is successful before you reload. Fix any errors it reports before continuing.

Step 5: Issue a Free TLS Certificate with Certbot

Certbot from the EFF automates Let's Encrypt certificate issuance and renewal. Make sure DNS is already resolving to your server before running this.

Install Certbot

# Debian / Ubuntu
sudo apt install certbot python3-certbot-nginx -y

# Fedora / RHEL / Rocky (enable EPEL first on RHEL/Rocky)
sudo dnf install epel-release -y   # RHEL/Rocky only
sudo dnf install certbot python3-certbot-nginx -y

# Arch
sudo pacman -S certbot certbot-nginx

Obtain and install the certificate

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

Certbot will ask for an email address and whether to redirect HTTP to HTTPS. Choose redirect (option 2) — it will rewrite your Nginx config automatically to add a 301 redirect and the ssl_certificate directives.

Verify automatic renewal

Certbot installs a systemd timer (on most distros) or a cron job to renew certificates automatically before they expire. Check the timer:

sudo systemctl status certbot.timer
# Or on systems using cron:
sudo certbot renew --dry-run

The dry run should complete with Congratulations, all simulated renewals succeeded. Certificates renew automatically every 60 days; Let's Encrypt certs expire after 90.

Step 6: Verify Everything End-to-End

Open a browser and navigate to https://example.com. You should see your test page served over HTTPS with a valid certificate. Also confirm the HTTP-to-HTTPS redirect works:

curl -I http://example.com

The response should include 301 Moved Permanently and a Location: https://example.com/ header. Check your certificate details:

echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -dates

This prints the notBefore and notAfter dates of the certificate — confirm the expiry is roughly 90 days out.

Step 7: Deploy Your Actual Site Content

Replace the test page with your real files. If you're deploying a static site, copy files directly:

rsync -avz ./my-site/ [email protected]:/var/www/example.com/html/

For a dynamic application (Node.js, Python, PHP, etc.), you'll run the app as its own systemd service and proxy requests through Nginx using an upstream block — that's a separate guide. For PHP specifically, install php-fpm and add a fastcgi_pass directive to your server block.

Troubleshooting

  • 502 Bad Gateway: Nginx is running but can't reach your application backend. Check that your app process is active with systemctl status your-app and that the socket or port in the Nginx proxy_pass matches.
  • Certbot says DNS doesn't resolve: DNS hasn't propagated yet, or your firewall blocks port 80. Recheck dig +short example.com A and sudo ufw status / firewall-cmd --list-all.
  • 403 Forbidden: Nginx can't read your files. Check permissions — the web root and all parent directories need execute permission for the www-data (Debian/Ubuntu) or nginx (Fedora/Arch) user. Run sudo chown -R www-data:www-data /var/www/example.com on Debian/Ubuntu.
  • Certificate not renewing: Run sudo certbot renew --dry-run and read the error. Common causes: port 80 blocked by firewall, or the Nginx config was edited and broke the .well-known location block Certbot added.
  • Nginx won't start after reboot: Confirm sudo systemctl is-enabled nginx returns enabled. If not, run sudo systemctl enable nginx.
tested on:Ubuntu 24.04Debian 12Fedora 40Rocky 9

Frequently asked questions

Do I need to open port 80 if I only want HTTPS?
Yes. Certbot's HTTP-01 challenge requires port 80 to be reachable during certificate issuance and renewal. You can redirect all HTTP traffic to HTTPS immediately after, but the port must stay open.
Can I use Apache instead of Nginx?
Absolutely. Install apache2 (Debian/Ubuntu) or httpd (Fedora/RHEL), use VirtualHost blocks in /etc/apache2/sites-available or /etc/httpd/conf.d, and install python3-certbot-apache instead of the Nginx plugin.
How do I host multiple websites on the same server?
Create a separate web root directory, virtual host config file, and run certbot for each domain. Nginx routes requests to the right block based on the server_name directive.
Why does Let's Encrypt only issue 90-day certificates?
Short lifetimes limit the damage from a compromised key and encourage automation. Certbot's systemd timer handles renewal automatically, so the short lifespan is a non-issue in practice.
My VPS provider has its own firewall — do I still need ufw or firewalld?
Provider-level firewalls (security groups on AWS, cloud firewalls on DigitalOcean) are separate from the OS-level firewall. It's good practice to run both, but at minimum ensure ports 22, 80, and 443 are open at the provider level before troubleshooting the OS firewall.

Related guides