Host Any Personal Project with Free DNS and HTTPS

A step-by-step guide to publishing a website or app on your own server, with a free subdomain from DuckDNS and a free, auto-renewing Let's Encrypt certificate.

Your domain (DuckDNS)→ Server IP (EC2)→ nginx→ Let's Encrypt HTTPS→ Your project

Overview

You will point a free DuckDNS subdomain at a server, serve your project's static files with nginx, and secure the connection with a Let's Encrypt certificate issued by Certbot. Certbot also renews the certificate automatically, so after setup you rarely need to touch it.

This works for any static site: a plain index.html, a React, Vue, or Svelte build, or a Vite or Astro output folder. For a backend app, see the proxy section at the end of this guide.

Requirements

  • A Linux server with a public IP address (for example, an AWS EC2 instance running Ubuntu).
  • SSH access and sudo privileges.
  • A DuckDNS account, free at duckdns.org, signed in with GitHub, Google, or Reddit.
  • Ports 80 and 443 open to the internet.

1. Free DNS with DuckDNS

  1. Sign in at duckdns.org.
  2. Choose a subdomain, such as myproject. Your full domain becomes myproject.duckdns.org.
  3. Enter your server's public IP in the current ip field and click update ip.
  4. Verify the record from your server:
bash
dig +short myproject.duckdns.org

The output should match your server's IP. DNS changes can take a few minutes to propagate.

Public IP changes EC2 public IPs change when an instance is stopped and started. To keep the same address, attach an Elastic IP in the EC2 console, then update DuckDNS with it.

2. Open ports 80 and 443

Let's Encrypt must reach your server on port 80 to verify that you control the domain. Port 443 serves HTTPS traffic. In the EC2 console, open the instance's security group and add these inbound rules:

TypeProtocolPortSource
HTTPTCP800.0.0.0/0
HTTPSTCP4430.0.0.0/0

If Ubuntu's firewall is enabled, allow the same ports:

bash
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw status

3. Install nginx and Certbot

bash
sudo apt update
sudo apt install -y nginx certbot python3-certbot-nginx
sudo systemctl enable --now nginx

Visit http://myproject.duckdns.org in a browser. You should see the default nginx welcome page. If you don't, check the security group and DNS.

4. Deploy your project

Plain HTML

If your project is a single file or a folder of static files, copy it to a web root:

bash
sudo mkdir -p /var/www/myproject
sudo cp index.html /var/www/myproject/

Build tools (React, Vite, Vue, and others)

Build the project locally or on the server, then copy the output folder. Common output folders are dist (Vite, Vue) and build (Create React App).

bash
npm ci
npm run build
sudo rm -rf /var/www/myproject
sudo cp -r dist /var/www/myproject

Make sure the web server can read the files:

bash
sudo chown -R www-data:www-data /var/www/myproject
sudo chmod -R 755 /var/www/myproject

5. Configure nginx

Create a site configuration for your domain. Replace myproject.duckdns.org and the root path with your own values.

/etc/nginx/sites-available/myproject
server {
    listen 80;
    listen [::]:80;
    server_name myproject.duckdns.org;

    root /var/www/myproject;
    index index.html;

    location / {
        try_files $uri $uri/ /index.html;
    }
}

Enable the site, remove the default one, check the syntax, and reload nginx:

bash
sudo ln -sf /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled/myproject
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx
Single-page apps The try_files line sends unknown routes to index.html, which is required for client-side routers such as React Router. Plain multi-page sites can use try_files $uri $uri/ =404; instead.

6. Get a free SSL certificate

Certbot's nginx plugin requests the certificate, edits your config, and adds the HTTP to HTTPS redirect for you:

bash
sudo certbot --nginx -d myproject.duckdns.org

Enter an email address for expiry notices, agree to the terms, and choose the option to redirect HTTP to HTTPS. Then test:

bash
curl -I http://myproject.duckdns.org    # expect 301 to https
curl -I https://myproject.duckdns.org   # expect 200
Tip You can also check the padlock in a browser or run a test at SSL Labs (ssllabs.com/ssltest).

7. Automatic renewal

Let's Encrypt certificates last 90 days. Certbot installs a systemd timer that renews them automatically. Verify it:

bash
systemctl list-timers | grep certbot
sudo certbot renew --dry-run

Because nginx runs on the host and Certbot's nginx plugin handles the challenge, renewal needs no downtime and no extra hooks.

8. Updating your site

Changes only require copying new files. nginx serves them immediately, and the certificate stays in place.

bash
cd ~/myproject
npm run build
sudo cp -r dist/* /var/www/myproject/

9. Hosting multiple projects

Each project needs its own DuckDNS subdomain, web root, and nginx site file. Repeat steps 1, 4, 5, and 6 for each one. Certbot can request a certificate covering several names at once:

bash
sudo certbot --nginx -d project1.duckdns.org -d project2.duckdns.org

Backend apps (Node, Python, and others)

If your project runs its own server on a local port, such as 3000, put nginx in front of it as a reverse proxy:

/etc/nginx/sites-available/api
server {
    listen 80;
    server_name api.duckdns.org;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Then run sudo certbot --nginx -d api.duckdns.org as before.

Troubleshooting

ProblemLikely cause and fix
Certbot: "Timeout during connect" Port 80 is blocked. Check the security group and ufw status.
Certbot: "Connection refused" Nothing is listening on port 80. Make sure nginx is running: sudo systemctl status nginx.
Domain shows the nginx welcome page The default site is still enabled, or your server_name doesn't match. Run sudo nginx -T to inspect the active config.
403 Forbidden File permissions are wrong. Run the chown and chmod commands from step 4.
404 on page refresh Add the try_files line for single-page apps.
nginx fails to reload Run sudo nginx -t to find the line with the error.
Certificate not trusted in browser DNS may still point to an old IP, or the certificate was not installed. Run sudo certbot certificates.