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.
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
sudoprivileges. - 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
- Sign in at duckdns.org.
- Choose a subdomain, such as
myproject. Your full domain becomesmyproject.duckdns.org. - Enter your server's public IP in the current ip field and click update ip.
- Verify the record from your server:
dig +short myproject.duckdns.org
The output should match your server's IP. DNS changes can take a few minutes to propagate.
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:
| Type | Protocol | Port | Source |
|---|---|---|---|
| HTTP | TCP | 80 | 0.0.0.0/0 |
| HTTPS | TCP | 443 | 0.0.0.0/0 |
If Ubuntu's firewall is enabled, allow the same ports:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw status
3. Install nginx and Certbot
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:
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).
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:
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.
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:
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
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:
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:
curl -I http://myproject.duckdns.org # expect 301 to https
curl -I https://myproject.duckdns.org # expect 200
7. Automatic renewal
Let's Encrypt certificates last 90 days. Certbot installs a systemd timer that renews them automatically. Verify it:
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.
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:
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:
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
| Problem | Likely 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. |