Back to blog

Build a Personal Blog — Phase 7: Custom Domain Setup

dnsdomainnginxdevopshosting
Build a Personal Blog — Phase 7: Custom Domain Setup

This is Phase 7 — the final phase of the Build a Personal Blog series. In Phase 6 you deployed your Dockerized blog to an Ubuntu VPS on Hostinger. It's live, it's secured with SSL, and it works. But visitors still access it via a raw IP address or a temporary domain. Time to fix that.

Series: Build a Personal Blog — Complete Roadmap
Previous: Phase 6 — Deploy to Ubuntu VPS
Source Code: GitHub — personal-blog-phase-7


What You'll Build

By the end of this phase:

✅ A custom .blog domain registered on Hostinger
DNS records pointing your domain to the VPS
DNS propagation verified with multiple tools
SSL certificates covering both yourdomain.com and www.yourdomain.com
www → apex redirect in Nginx for a clean canonical URL
Google Search Console submission with sitemap
Social sharing previews verified and working

Time commitment: 1–2 hours (plus DNS propagation wait time)
Prerequisites: Phase 6 — Deploy to Ubuntu VPS


Architecture Overview

Here's the full picture — from a user typing your domain in the browser to your blog rendering the page:

The domain registrar (Hostinger) tells the DNS system where your server lives. DNS resolves the domain name to your VPS IP. Nginx handles SSL and redirects www traffic to the apex domain.


1. Register a Domain on Hostinger

1.1 Choose Your Domain

Head to Hostinger Domains and search for your desired domain. Some tips:

  • .blog domains are purpose-built for blogs and relatively affordable (~$10-15/year)
  • .dev domains are great for developer portfolios (~$12-15/year)
  • .com is the classic choice if available (~$10/year)
  • Keep it short, memorable, and easy to spell

For this guide, we'll use chanh.blog as the example domain. Replace it with your actual domain throughout.

1.2 Complete the Purchase

  1. Add the domain to your cart
  2. Choose the registration period (1 year is fine to start)
  3. Privacy protection: Hostinger includes WHOIS privacy for free — keep it enabled
  4. Complete payment

After purchase, the domain appears in your Hostinger dashboard under Domains.

1.3 Hostinger DNS Management Panel

Navigate to Domains → chanh.blog → DNS / Nameservers in the Hostinger panel. This is where you'll add DNS records.

By default, Hostinger sets its own nameservers:

ns1.dns-parking.com
ns2.dns-parking.com

These will change to Hostinger's proper nameservers once you start configuring DNS records. If you're using Hostinger for both VPS hosting and domain registration, the default nameservers work fine.


2. Configure DNS Records

DNS records tell the internet where to find your server when someone types your domain name. You need two types of records.

2.1 What is an A Record?

An A record (Address record) maps a domain name to an IPv4 address. When someone visits chanh.blog, the DNS system looks up the A record and returns your VPS IP address.

2.2 Add DNS Records

In the Hostinger DNS management panel, add these records:

TypeNameValueTTL
A@YOUR_VPS_IP3600
AwwwYOUR_VPS_IP3600

Explanation:

  • @ represents the apex domain (chanh.blog) — this is the record that makes chanh.blog point to your server
  • www handles the www.chanh.blog subdomain — we'll redirect this to the apex domain later
  • TTL 3600 means DNS resolvers cache this record for 1 hour (3600 seconds). Lower TTL = faster propagation when you change records, but more DNS lookups

Tip: If your VPS also has an IPv6 address, add AAAA records with the same names pointing to the IPv6 address. This enables IPv6 connectivity for users on modern networks.

2.3 Optional: MX Records for Email

If you want to receive email at you@chanh.blog, you'll need MX records. This is beyond our scope, but the setup varies by email provider:

  • Google Workspace: Add Google's MX records
  • Zoho Mail (free tier): Add Zoho's MX records
  • Fastmail, ProtonMail, etc.: Follow their DNS setup guides

For now, skip this — you can always add email later.

2.4 Remove Conflicting Records

Hostinger may have default records (like parking pages or CNAME records) that conflict with your new A records. In the DNS panel:

  1. Look for any existing A or CNAME records for @ or www
  2. Delete them if they point to anything other than your VPS IP
  3. Keep the NS (nameserver) records — those are required

3. DNS Propagation

After adding DNS records, you need to wait for the changes to propagate across the global DNS system. This can take anywhere from 5 minutes to 48 hours, though it's usually 15-30 minutes with Hostinger.

3.1 Check Propagation with dig

dig is the standard DNS lookup tool. Run it from your local machine:

# Check A record for apex domain
dig chanh.blog +short
# Expected: YOUR_VPS_IP
 
# Check A record for www
dig www.chanh.blog +short
# Expected: YOUR_VPS_IP
 
# Full DNS trace (shows the resolution path)
dig chanh.blog +trace

3.2 Check with nslookup

nslookup chanh.blog
# Expected output:
# Name:    chanh.blog
# Address: YOUR_VPS_IP

3.3 Online Propagation Checkers

These tools check DNS from multiple locations worldwide:

Enter your domain and check the A record. You should see your VPS IP appearing across all locations. If some locations still show the old value, wait a bit longer.

3.4 What If DNS Isn't Propagating?

Common issues:

ProblemSolution
Wrong IP in A recordDouble-check the VPS IP in Hostinger dashboard
CNAME conflictRemove any CNAME record for @ — CNAME and A can't coexist on the apex
Still showing old IPLower TTL to 300, wait, then change the record
No response at allVerify nameservers are correctly set in Hostinger

4. Update Nginx for Your Domain

In Phase 6, your Nginx config used the VPS IP or a placeholder domain. Now update it with your real domain.

4.1 Edit the Nginx Configuration

SSH into your VPS and edit the blog config:

ssh deploy@YOUR_VPS_IP
sudo nano /etc/nginx/sites-available/blog

Replace the existing server block with this configuration:

# Redirect www to apex domain
server {
    listen 80;
    listen [::]:80;
    server_name www.chanh.blog;
    return 301 https://chanh.blog$request_uri;
}
 
# Main server block
server {
    listen 80;
    listen [::]:80;
    server_name chanh.blog;
 
    # Proxy all requests to Next.js
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        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;
 
        # Timeouts
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }
 
    # Cache static assets
    location /_next/static/ {
        proxy_pass http://127.0.0.1:3000;
        proxy_cache_valid 200 365d;
        add_header Cache-Control "public, max-age=31536000, immutable";
    }
 
    # Cache images
    location /images/ {
        proxy_pass http://127.0.0.1:3000;
        proxy_cache_valid 200 30d;
        add_header Cache-Control "public, max-age=2592000";
    }
}

Replace chanh.blog with your actual domain.

4.2 Test and Reload

# Test for syntax errors
sudo nginx -t
 
# Reload configuration
sudo systemctl reload nginx

4.3 Why Redirect www to Apex?

Having both www.chanh.blog and chanh.blog serve the same content creates duplicate content issues for search engines. Pick one as your canonical URL and redirect the other. The apex domain (without www) is the modern standard:

A 301 redirect tells search engines "this page has permanently moved" — they'll index only the apex domain.


5. SSL Certificate for Your Domain

In Phase 6, you may have set up SSL with Let's Encrypt using your VPS IP or a temporary domain. Now you need a certificate that covers your actual domain.

5.1 Issue a New Certificate

sudo certbot --nginx -d chanh.blog -d www.chanh.blog

Certbot will:

  1. Verify you own both chanh.blog and www.chanh.blog via HTTP challenge
  2. Issue a certificate covering both domains
  3. Automatically update your Nginx config with SSL settings
  4. Set up HTTP → HTTPS redirect

Important: DNS must be fully propagated before running Certbot. If Certbot can't reach your server via the domain name, the HTTP challenge fails. Use dig chanh.blog +short to verify your VPS IP is returned.

5.2 Verify the Updated Nginx Config

After Certbot runs, check what it added:

cat /etc/nginx/sites-available/blog

You should see:

  • listen 443 ssl directives
  • Certificate paths (/etc/letsencrypt/live/chanh.blog/)
  • Automatic HTTP → HTTPS redirect on port 80
  • The www → apex redirect now also handles HTTPS

Certbot modifies your config intelligently. The www server block will redirect to https://chanh.blog, and the main block will serve over HTTPS.

5.3 Test SSL

Open your browser and verify:

# Should load your blog over HTTPS
curl -I https://chanh.blog
 
# Should redirect to https://chanh.blog
curl -I http://chanh.blog
 
# Should redirect to https://chanh.blog
curl -I https://www.chanh.blog
 
# Should redirect to https://chanh.blog
curl -I http://www.chanh.blog

All four URLs should ultimately land on https://chanh.blog with your blog content.

5.4 SSL Labs Test

For a thorough SSL check, visit SSL Labs and enter chanh.blog. You should get an A or A+ rating. Common issues that lower the score:

IssueFix
HSTS not enabledAdd add_header Strict-Transport-Security to Nginx
Weak cipher suitesCertbot usually handles this, but check your Nginx SSL config
Certificate chain incompleteRe-run certbot --nginx to fix

5.5 Auto-Renewal Confirmation

Verify the renewal timer is active:

sudo systemctl status certbot.timer
# Should show "active (waiting)"
 
# Test renewal
sudo certbot renew --dry-run

Certificates renew automatically every 60-90 days. No manual action needed.


6. Update Application Configuration

Your blog's application code may reference the old URL. Update it to use your new domain.

6.1 Update Environment Variables

On the VPS, edit your production environment file:

cd ~/my-blog
nano .env.production

Update the site URL:

NEXT_PUBLIC_SITE_URL=https://chanh.blog

6.2 Rebuild the Application

The site URL is baked into the build at compile time (it's a NEXT_PUBLIC_ variable). Rebuild:

./deploy.sh

Or manually:

docker compose -f docker-compose.yml -f docker-compose.prod.yml \
  --env-file .env.production \
  up -d --build

6.3 Verify Meta Tags

After rebuilding, check that Open Graph meta tags use the correct domain:

curl -s https://chanh.blog | grep -E 'og:url|og:image|canonical'

You should see your new domain in all meta tag URLs — not the old IP or temporary domain.


7. End-to-End Testing

Your domain is configured, SSL is active, and the app is rebuilt. Time to verify everything works.

7.1 Core Functionality Checklist

Test each of these in your browser:

TestURLExpected
Home pagehttps://chanh.blogBlog home loads over HTTPS
Blog listinghttps://chanh.blog/blogAll posts visible
Single posthttps://chanh.blog/blog/your-postPost renders with syntax highlighting
Tag pagehttps://chanh.blog/blog?tag=dockerFiltered posts display
Searchhttps://chanh.blog/blog?q=nginxSearch results appear
RSS feedhttps://chanh.blog/feed.xmlValid XML feed
Sitemaphttps://chanh.blog/sitemap.xmlAll pages listed
www redirecthttps://www.chanh.blogRedirects to https://chanh.blog
HTTP redirecthttp://chanh.blogRedirects to https://chanh.blog

7.2 Mobile Testing

Open your blog on a phone or use Chrome DevTools' device simulator (F12 → Toggle Device Toolbar). Verify:

  • Responsive layout works
  • Navigation menu opens/closes
  • Posts are readable on small screens
  • Images don't overflow the viewport

7.3 Performance Check

Run a quick performance audit with PageSpeed Insights. Enter https://chanh.blog and check:

  • Performance: 90+ is good, 95+ is great
  • Accessibility: Should be 90+
  • SEO: Should be 90+
  • Best Practices: Should be 90+

8. Post-Launch Checklist

Your blog is live on a custom domain. Here's what to do next to maximize visibility.

8.1 Google Search Console

Google Search Console lets you monitor how Google crawls and indexes your site.

  1. Go to Google Search Console
  2. Click Add Property
  3. Choose URL prefix and enter https://chanh.blog
  4. Verify ownership — the easiest method:
    • Download the HTML verification file Google provides
    • Place it in your public/ directory
    • Commit and deploy
    • Click "Verify" in Search Console
  5. After verification:
    • Submit your sitemap: go to Sitemaps → enter https://chanh.blog/sitemap.xml → click Submit
    • Request indexing for your homepage: enter the URL in the top search bar → click Request Indexing

8.2 Social Sharing Previews

When you share a link on Twitter/X, LinkedIn, or Facebook, they show a preview card with your post's title, description, and OG image. Verify these work:

Enter your blog URL or a specific post URL. You should see:

  • Post title
  • Description
  • OG image (the custom image you generated)

If the preview looks wrong, check your <meta> tags:

curl -s https://chanh.blog/blog/your-post | grep -E 'og:|twitter:'

8.3 Analytics (Optional)

Consider adding analytics to track visitors. Privacy-friendly options:

  • Plausible Analytics — lightweight, privacy-first, no cookies (~$9/month)
  • Umami — self-hosted, open source, free
  • Google Analytics — free but uses cookies, requires consent banner in EU

For a self-hosted approach, Umami pairs well with your existing Docker setup — you can add it as another service in docker-compose.yml.

8.4 robots.txt

If you don't already have one, add a robots.txt to guide search engine crawlers:

Create public/robots.txt:

User-agent: *
Allow: /
 
Sitemap: https://chanh.blog/sitemap.xml

This tells all crawlers they can index everything, and points them to your sitemap.


9. Optional: Cloudflare DNS Proxy

If you want an extra layer of protection, you can put Cloudflare in front of your domain. Cloudflare's free tier gives you:

  • DDoS protection — absorbs volumetric attacks
  • CDN caching — serves static assets from edge servers worldwide
  • DNS analytics — see query volume and response times
  • Always Online — shows a cached version if your server goes down

9.1 Set Up Cloudflare

  1. Create a free account at cloudflare.com
  2. Add your domain (chanh.blog)
  3. Cloudflare scans your existing DNS records
  4. Review and confirm the imported records match what you set in Hostinger
  5. Cloudflare gives you two nameservers (e.g., ada.ns.cloudflare.com, bob.ns.cloudflare.com)
  6. In Hostinger → Domains → DNS / Nameservers, change the nameservers to Cloudflare's
  7. Wait for nameserver propagation (can take up to 24 hours)

9.2 Cloudflare SSL Mode

Important: Set Cloudflare's SSL mode to Full (Strict). This ensures:

  • Cloudflare ↔ Your Server: encrypted with your Let's Encrypt certificate
  • Browser ↔ Cloudflare: encrypted with Cloudflare's certificate

Do not use "Flexible" SSL — it means Cloudflare connects to your server over plain HTTP, which defeats the purpose of having SSL on your VPS.

9.3 When to Skip Cloudflare

Cloudflare adds complexity. For a personal blog with moderate traffic, it's optional. You might skip it if:

  • You want simpler DNS management (one fewer service to configure)
  • You're not concerned about DDoS attacks
  • You prefer to keep your infrastructure minimal

Your VPS with Nginx + Let's Encrypt is already a solid setup. Cloudflare is a nice-to-have, not a must-have.


Troubleshooting

Domain doesn't resolve

# Check DNS records
dig chanh.blog +short
# Should return your VPS IP
 
# If empty, DNS hasn't propagated yet
# Check propagation status at whatsmydns.net

Common causes:

  • DNS records not saved properly in Hostinger
  • Nameserver change still propagating (up to 48 hours)
  • Typo in the domain name or IP address

Certbot fails with "Could not reach domain"

# Verify DNS points to your VPS
dig chanh.blog +short
 
# Verify port 80 is open
sudo ufw status
 
# Verify Nginx is running and listening
sudo nginx -t
sudo systemctl status nginx
 
# Try Certbot with verbose output
sudo certbot --nginx -d chanh.blog -d www.chanh.blog -v

Certbot needs to access http://chanh.blog/.well-known/acme-challenge/ to verify ownership. If DNS isn't propagated or port 80 is blocked, it fails.

www doesn't redirect

Check your Nginx config has the www server block with the 301 redirect:

cat /etc/nginx/sites-available/blog | grep -A5 "www\."

If the www block is missing, re-edit the config and add it (see Section 4.1).

OG images show old domain

After changing NEXT_PUBLIC_SITE_URL, you must rebuild:

cd ~/my-blog
./deploy.sh

Then clear the cache on social platforms:

  • Facebook: Use the Sharing Debugger and click "Scrape Again"
  • LinkedIn: Use Post Inspector to refresh
  • Twitter/X: Cards update automatically within a few hours

Summary

In this final phase you:

Registered a custom domain on Hostinger and configured the DNS management panel
Added A records for both the apex domain and www subdomain
Verified DNS propagation with dig, nslookup, and online tools
Updated Nginx with your domain name and a www → apex redirect
Issued SSL certificates covering both chanh.blog and www.chanh.blog
Updated application config with the new NEXT_PUBLIC_SITE_URL
Tested end-to-end — HTTPS, redirects, posts, search, and feeds
Submitted to Google Search Console with sitemap for indexing
Verified social sharing previews on Twitter/X, LinkedIn, and Facebook

Your blog is complete. From an empty folder to a live, self-hosted blog on a custom domain — you built the entire stack yourself.


Series Complete!

Congratulations! You've completed the entire Build a Personal Blog series. Here's everything you built across 8 posts:

PhaseWhat You BuiltPost
OverviewSeries roadmap and architectureBuild a Personal Blog — Roadmap
Phase 1Next.js 16 + ShadCN/UI project setupProject Setup
Phase 2MDX on-demand rendering pipelineMDX Rendering
Phase 3PostgreSQL + Drizzle ORM integrationDatabase Layer
Phase 4Tags, search, and paginationBlog Features
Phase 5Docker Compose for dev and productionDocker Compose
Phase 6Deploy to Ubuntu VPS on HostingerVPS Deployment
Phase 7Custom domain setup and launchYou are here

You now have the skills to build, deploy, and maintain a production web application from scratch. The same patterns apply to any project — not just blogs. Docker, Nginx, SSL, DNS, and VPS management are fundamental DevOps skills that transfer everywhere.


Series Index

PostTitleStatus
BLOG-1Build a Personal Blog — Roadmap✅ Complete
BLOG-2Phase 1: Project Setup — Next.js 16 + ShadCN/UI✅ Complete
BLOG-3Phase 2: MDX On-Demand Rendering✅ Complete
BLOG-4Phase 3: PostgreSQL + Drizzle ORM✅ Complete
BLOG-5Phase 4: Tags, Search & Pagination✅ Complete
BLOG-6Phase 5: Docker Compose✅ Complete
BLOG-7Phase 6: Deploy to Ubuntu VPS on Hostinger✅ Complete
BLOG-8Phase 7: Custom Domain Setup on Hostinger✅ You are here

📬 Subscribe to Newsletter

Get the latest blog posts delivered to your inbox every week. No spam, unsubscribe anytime.

We respect your privacy. Unsubscribe at any time.

💬 Comments

Sign in to leave a comment

We'll never post without your permission.