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:
.blogdomains are purpose-built for blogs and relatively affordable (~$10-15/year).devdomains are great for developer portfolios (~$12-15/year).comis 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
- Add the domain to your cart
- Choose the registration period (1 year is fine to start)
- Privacy protection: Hostinger includes WHOIS privacy for free — keep it enabled
- 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.comThese 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:
| Type | Name | Value | TTL |
|---|---|---|---|
| A | @ | YOUR_VPS_IP | 3600 |
| A | www | YOUR_VPS_IP | 3600 |
Explanation:
@represents the apex domain (chanh.blog) — this is the record that makeschanh.blogpoint to your serverwwwhandles thewww.chanh.blogsubdomain — 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:
- Look for any existing A or CNAME records for
@orwww - Delete them if they point to anything other than your VPS IP
- 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 +trace3.2 Check with nslookup
nslookup chanh.blog
# Expected output:
# Name: chanh.blog
# Address: YOUR_VPS_IP3.3 Online Propagation Checkers
These tools check DNS from multiple locations worldwide:
- whatsmydns.net — visual propagation map
- dnschecker.org — checks from 20+ locations
- mxtoolbox.com — detailed DNS analysis
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:
| Problem | Solution |
|---|---|
| Wrong IP in A record | Double-check the VPS IP in Hostinger dashboard |
| CNAME conflict | Remove any CNAME record for @ — CNAME and A can't coexist on the apex |
| Still showing old IP | Lower TTL to 300, wait, then change the record |
| No response at all | Verify 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/blogReplace 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 nginx4.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.blogCertbot will:
- Verify you own both
chanh.blogandwww.chanh.blogvia HTTP challenge - Issue a certificate covering both domains
- Automatically update your Nginx config with SSL settings
- 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 +shortto 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/blogYou should see:
listen 443 ssldirectives- 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.blogAll 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:
| Issue | Fix |
|---|---|
| HSTS not enabled | Add add_header Strict-Transport-Security to Nginx |
| Weak cipher suites | Certbot usually handles this, but check your Nginx SSL config |
| Certificate chain incomplete | Re-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-runCertificates 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.productionUpdate the site URL:
NEXT_PUBLIC_SITE_URL=https://chanh.blog6.2 Rebuild the Application
The site URL is baked into the build at compile time (it's a NEXT_PUBLIC_ variable). Rebuild:
./deploy.shOr manually:
docker compose -f docker-compose.yml -f docker-compose.prod.yml \
--env-file .env.production \
up -d --build6.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:
| Test | URL | Expected |
|---|---|---|
| Home page | https://chanh.blog | Blog home loads over HTTPS |
| Blog listing | https://chanh.blog/blog | All posts visible |
| Single post | https://chanh.blog/blog/your-post | Post renders with syntax highlighting |
| Tag page | https://chanh.blog/blog?tag=docker | Filtered posts display |
| Search | https://chanh.blog/blog?q=nginx | Search results appear |
| RSS feed | https://chanh.blog/feed.xml | Valid XML feed |
| Sitemap | https://chanh.blog/sitemap.xml | All pages listed |
| www redirect | https://www.chanh.blog | Redirects to https://chanh.blog |
| HTTP redirect | http://chanh.blog | Redirects 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.
- Go to Google Search Console
- Click Add Property
- Choose URL prefix and enter
https://chanh.blog - 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
- 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
- Submit your sitemap: go to Sitemaps → enter
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:
- Twitter/X: Card Validator
- LinkedIn: Post Inspector
- Facebook: Sharing Debugger
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.xmlThis 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
- Create a free account at cloudflare.com
- Add your domain (
chanh.blog) - Cloudflare scans your existing DNS records
- Review and confirm the imported records match what you set in Hostinger
- Cloudflare gives you two nameservers (e.g.,
ada.ns.cloudflare.com,bob.ns.cloudflare.com) - In Hostinger → Domains → DNS / Nameservers, change the nameservers to Cloudflare's
- 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.netCommon 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 -vCertbot 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.shThen 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:
| Phase | What You Built | Post |
|---|---|---|
| Overview | Series roadmap and architecture | Build a Personal Blog — Roadmap |
| Phase 1 | Next.js 16 + ShadCN/UI project setup | Project Setup |
| Phase 2 | MDX on-demand rendering pipeline | MDX Rendering |
| Phase 3 | PostgreSQL + Drizzle ORM integration | Database Layer |
| Phase 4 | Tags, search, and pagination | Blog Features |
| Phase 5 | Docker Compose for dev and production | Docker Compose |
| Phase 6 | Deploy to Ubuntu VPS on Hostinger | VPS Deployment |
| Phase 7 | Custom domain setup and launch | You 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
| Post | Title | Status |
|---|---|---|
| BLOG-1 | Build a Personal Blog — Roadmap | ✅ Complete |
| BLOG-2 | Phase 1: Project Setup — Next.js 16 + ShadCN/UI | ✅ Complete |
| BLOG-3 | Phase 2: MDX On-Demand Rendering | ✅ Complete |
| BLOG-4 | Phase 3: PostgreSQL + Drizzle ORM | ✅ Complete |
| BLOG-5 | Phase 4: Tags, Search & Pagination | ✅ Complete |
| BLOG-6 | Phase 5: Docker Compose | ✅ Complete |
| BLOG-7 | Phase 6: Deploy to Ubuntu VPS on Hostinger | ✅ Complete |
| BLOG-8 | Phase 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.