Back to blog

Build a Personal Blog — Phase 7: Setup Custom Domain

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

Đây là Phase 7 — phase cuối cùng của series Build a Personal Blog. Ở Phase 6, bạn đã deploy blog Docker hóa lên Ubuntu VPS trên Hostinger. Blog đã live, đã có SSL, và hoạt động ngon lành. Nhưng visitors vẫn truy cập qua IP address hoặc domain tạm. Giờ là lúc fix chuyện đó.

Series: Build a Personal Blog — Complete Roadmap
Trước: Phase 6 — Deploy lên Ubuntu VPS
Source Code: GitHub — personal-blog-phase-7


Bạn Sẽ Build Được Gì

Sau phase này:

✅ Custom domain .blog đã đăng ký trên Hostinger
✅ DNS records trỏ domain về VPS
✅ DNS propagation đã verify bằng nhiều tool
✅ SSL certificates cover cả yourdomain.com và www.yourdomain.com
✅ www → apex redirect trong Nginx cho canonical URL gọn gàng
✅ Google Search Console đã submit với sitemap
✅ Social sharing previews đã verify và hoạt động

Time commitment: 1–2 giờ (cộng thời gian chờ DNS propagation)
Prerequisites: Phase 6 — Deploy lên Ubuntu VPS


Tổng Quan Kiến Trúc

Đây là bức tranh toàn cảnh — từ user gõ domain trên browser đến blog render trang:

Domain registrar (Hostinger) báo cho hệ thống DNS biết server bạn ở đâu. DNS resolve tên miền thành VPS IP. Nginx xử lý SSL và redirect traffic www về apex domain.


1. Đăng Ký Domain Trên Hostinger

1.1 Chọn Domain

Vào Hostinger Domains và tìm domain bạn muốn. Một vài tips:

  • .blog — sinh ra để làm blog, giá khá mềm (~$10-15/năm)
  • .dev — ngon cho developer portfolio (~$12-15/năm)
  • .com — classic choice nếu còn available (~$10/năm)
  • Chọn tên ngắn, dễ nhớ, dễ đánh vần

Trong guide này, mình dùng chanh.blog làm ví dụ. Thay bằng domain thật của bạn nhé.

1.2 Hoàn Tất Mua Hàng

  1. Add domain vào cart
  2. Chọn thời hạn đăng ký (1 năm là đủ để bắt đầu)
  3. Privacy protection: Hostinger cho WHOIS privacy miễn phí — cứ bật
  4. Thanh toán

Sau khi mua, domain xuất hiện trong Hostinger dashboard ở mục Domains.

1.3 Bảng Quản Lý DNS Hostinger

Vào Domains → chanh.blog → DNS / Nameservers trong Hostinger panel. Đây là nơi bạn sẽ thêm DNS records.

Mặc định, Hostinger set nameservers riêng:

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

Các nameservers này sẽ đổi thành nameservers chính thức của Hostinger khi bạn bắt đầu cấu hình DNS records. Nếu bạn dùng Hostinger cho cả VPS hosting lẫn domain registration thì nameservers mặc định là ổn.


2. Cấu Hình DNS Records

DNS records báo cho internet biết server bạn ở đâu khi ai đó gõ tên miền. Bạn cần hai loại records.

2.1 A Record Là Gì?

A record (Address record) map tên miền sang IPv4 address. Khi ai đó truy cập chanh.blog, hệ thống DNS tra cứu A record và trả về VPS IP của bạn.

2.2 Thêm DNS Records

Trong bảng quản lý DNS Hostinger, thêm các records sau:

TypeNameValueTTL
A@YOUR_VPS_IP3600
AwwwYOUR_VPS_IP3600

Giải thích:

  • @ đại diện cho apex domain (chanh.blog) — record này làm cho chanh.blog trỏ về server bạn
  • www xử lý subdomain www.chanh.blog — mình sẽ redirect cái này về apex domain sau
  • TTL 3600 nghĩa là DNS resolvers cache record này trong 1 giờ (3600 giây). TTL thấp hơn = propagation nhanh hơn khi bạn đổi records, nhưng nhiều DNS lookups hơn

Tip: Nếu VPS có IPv6 address, thêm AAAA records cùng tên trỏ về IPv6 address. Như vậy users trên mạng hiện đại có thể kết nối qua IPv6.

2.3 Tùy Chọn: MX Records Cho Email

Nếu bạn muốn nhận email tại [email protected], bạn cần MX records. Ngoài phạm vi bài này, nhưng setup tùy theo email provider:

  • Google Workspace: Thêm MX records của Google
  • Zoho Mail (gói miễn phí): Thêm MX records của Zoho
  • Fastmail, ProtonMail, v.v.: Làm theo hướng dẫn DNS setup của họ

Giờ thì skip — bạn luôn có thể thêm email sau.

2.4 Xóa Records Xung Đột

Hostinger có thể có records mặc định (như parking pages hoặc CNAME records) xung đột với A records mới. Trong DNS panel:

  1. Tìm các A hoặc CNAME records hiện tại cho @ hoặc www
  2. Xóa nếu chúng trỏ đến đâu đó khác ngoài VPS IP của bạn
  3. Giữ lại NS (nameserver) records — chúng bắt buộc phải có

3. DNS Propagation

Sau khi thêm DNS records, bạn cần chờ thay đổi lan truyền qua hệ thống DNS toàn cầu. Có thể mất từ 5 phút đến 48 giờ, nhưng thường là 15-30 phút với Hostinger.

3.1 Kiểm Tra Với dig

dig là tool tra cứu DNS tiêu chuẩn. Chạy trên máy local:

# Kiểm tra A record cho apex domain
dig chanh.blog +short
# Kỳ vọng: YOUR_VPS_IP
 
# Kiểm tra A record cho www
dig www.chanh.blog +short
# Kỳ vọng: YOUR_VPS_IP
 
# Full DNS trace (hiển thị đường đi resolution)
dig chanh.blog +trace

3.2 Kiểm Tra Với nslookup

nslookup chanh.blog
# Output kỳ vọng:
# Name:    chanh.blog
# Address: YOUR_VPS_IP

3.3 Online Propagation Checkers

Những tool này kiểm tra DNS từ nhiều vị trí toàn cầu:

Nhập domain và kiểm tra A record. Bạn sẽ thấy VPS IP xuất hiện ở tất cả vị trí. Nếu một số nơi vẫn hiện giá trị cũ, chờ thêm chút nữa.

3.4 DNS Không Propagate Thì Sao?

Các vấn đề thường gặp:

Vấn ĐềGiải Pháp
IP sai trong A recordKiểm tra lại VPS IP trong Hostinger dashboard
CNAME xung độtXóa CNAME record cho @ — CNAME và A không thể cùng tồn tại trên apex
Vẫn hiện IP cũHạ TTL xuống 300, chờ, rồi đổi record
Không có responseXác nhận nameservers đã set đúng trong Hostinger

4. Cập Nhật Nginx Cho Domain

Ở Phase 6, Nginx config của bạn dùng VPS IP hoặc domain placeholder. Giờ update bằng domain thật.

4.1 Sửa Cấu Hình Nginx

SSH vào VPS và sửa blog config:

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

Thay server block hiện tại bằng config này:

# Redirect www sang apex domain
server {
    listen 80;
    listen [::]:80;
    server_name www.chanh.blog;
    return 301 https://chanh.blog$request_uri;
}
 
# Server block chính
server {
    listen 80;
    listen [::]:80;
    server_name chanh.blog;
 
    # Proxy mọi requests đến 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";
    }
}

Thay chanh.blog bằng domain thật của bạn.

4.2 Test Và Reload

# Test lỗi syntax
sudo nginx -t
 
# Reload cấu hình
sudo systemctl reload nginx

4.3 Tại Sao Redirect www Về Apex?

Cả www.chanh.blog và chanh.blog cùng serve content giống nhau sẽ tạo vấn đề duplicate content cho search engines. Chọn một cái làm canonical URL và redirect cái còn lại. Apex domain (không có www) là chuẩn hiện đại:

301 redirect báo cho search engines "trang này đã chuyển vĩnh viễn" — chúng sẽ chỉ index apex domain.


5. SSL Certificate Cho Domain

Ở Phase 6, bạn có thể đã setup SSL với Let's Encrypt bằng VPS IP hoặc domain tạm. Giờ bạn cần certificate cover domain thật.

5.1 Cấp Certificate Mới

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

Certbot sẽ:

  1. Verify bạn sở hữu cả chanh.blog và www.chanh.blog qua HTTP challenge
  2. Cấp certificate cover cả hai domains
  3. Tự động update Nginx config với SSL settings
  4. Setup redirect HTTP → HTTPS

Quan trọng: DNS phải propagate hoàn toàn trước khi chạy Certbot. Nếu Certbot không thể truy cập server qua domain name, HTTP challenge sẽ fail. Dùng dig chanh.blog +short để verify VPS IP đã được trả về.

5.2 Verify Nginx Config Đã Cập Nhật

Sau khi Certbot chạy xong, kiểm tra những gì nó thêm vào:

cat /etc/nginx/sites-available/blog

Bạn sẽ thấy:

  • listen 443 ssl directives
  • Đường dẫn certificate (/etc/letsencrypt/live/chanh.blog/)
  • Redirect tự động HTTP → HTTPS trên port 80
  • www → apex redirect giờ cũng xử lý HTTPS

Certbot modify config một cách thông minh. www server block sẽ redirect đến https://chanh.blog, và block chính sẽ serve qua HTTPS.

5.3 Test SSL

Mở browser và verify:

# Phải load blog qua HTTPS
curl -I https://chanh.blog
 
# Phải redirect sang https://chanh.blog
curl -I http://chanh.blog
 
# Phải redirect sang https://chanh.blog
curl -I https://www.chanh.blog
 
# Phải redirect sang https://chanh.blog
curl -I http://www.chanh.blog

Cả bốn URLs cuối cùng phải đến https://chanh.blog với nội dung blog của bạn.

5.4 SSL Labs Test

Để kiểm tra SSL kỹ lưỡng, vào SSL Labs và nhập chanh.blog. Bạn nên đạt A hoặc A+. Những vấn đề thường gặp làm hạ điểm:

Vấn ĐềCách Fix
HSTS chưa bậtThêm add_header Strict-Transport-Security vào Nginx
Cipher suites yếuCertbot thường xử lý rồi, nhưng kiểm tra Nginx SSL config
Certificate chain không đầy đủChạy lại certbot --nginx để fix

5.5 Xác Nhận Auto-Renewal

Verify renewal timer đang active:

sudo systemctl status certbot.timer
# Phải hiện "active (waiting)"
 
# Test renewal
sudo certbot renew --dry-run

Certificates tự động renew mỗi 60-90 ngày. Không cần thao tác thủ công.


6. Cập Nhật Cấu Hình Ứng Dụng

Code blog của bạn có thể đang reference URL cũ. Update để dùng domain mới.

6.1 Update Environment Variables

Trên VPS, sửa file production environment:

cd ~/my-blog
nano .env.production

Update site URL:

NEXT_PUBLIC_SITE_URL=https://chanh.blog

6.2 Rebuild Ứng Dụng

Site URL được bake vào build lúc compile (vì là biến NEXT_PUBLIC_). Phải rebuild:

./deploy.sh

Hoặc chạy thủ công:

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

6.3 Verify Meta Tags

Sau khi rebuild, kiểm tra Open Graph meta tags dùng đúng domain:

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

Bạn phải thấy domain mới trong tất cả meta tag URLs — không phải IP cũ hay domain tạm.


7. Test End-to-End

Domain đã cấu hình, SSL đã active, và app đã rebuild. Giờ verify mọi thứ hoạt động.

7.1 Checklist Chức Năng Chính

Test từng mục trong browser:

TestURLKỳ Vọng
Trang chủhttps://chanh.blogBlog home load qua HTTPS
Danh sách bài viếthttps://chanh.blog/blogTất cả posts hiển thị
Bài viết đơnhttps://chanh.blog/blog/your-postPost render với syntax highlighting
Trang taghttps://chanh.blog/blog?tag=dockerPosts đã filter hiển thị
Tìm kiếmhttps://chanh.blog/blog?q=nginxKết quả tìm kiếm xuất hiện
RSS feedhttps://chanh.blog/feed.xmlXML feed hợp lệ
Sitemaphttps://chanh.blog/sitemap.xmlTất cả pages được liệt kê
www redirecthttps://www.chanh.blogRedirect sang https://chanh.blog
HTTP redirecthttp://chanh.blogRedirect sang https://chanh.blog

7.2 Test Mobile

Mở blog trên điện thoại hoặc dùng Chrome DevTools device simulator (F12 → Toggle Device Toolbar). Verify:

  • Responsive layout hoạt động
  • Navigation menu mở/đóng bình thường
  • Posts đọc được trên màn hình nhỏ
  • Images không tràn viewport

7.3 Kiểm Tra Performance

Chạy performance audit nhanh với PageSpeed Insights. Nhập https://chanh.blog và kiểm tra:

  • Performance: 90+ là tốt, 95+ là tuyệt vời
  • Accessibility: Nên đạt 90+
  • SEO: Nên đạt 90+
  • Best Practices: Nên đạt 90+

8. Checklist Post-Launch

Blog đã live trên custom domain. Đây là những gì cần làm tiếp để tối đa visibility.

8.1 Google Search Console

Google Search Console cho phép bạn monitor cách Google crawl và index site.

  1. Vào Google Search Console
  2. Click Add Property
  3. Chọn URL prefix và nhập https://chanh.blog
  4. Verify ownership — cách đơn giản nhất:
    • Download file HTML verification mà Google cung cấp
    • Đặt vào thư mục public/
    • Commit và deploy
    • Click "Verify" trong Search Console
  5. Sau khi verify:
    • Submit sitemap: vào Sitemaps → nhập https://chanh.blog/sitemap.xml → click Submit
    • Request indexing cho homepage: nhập URL vào thanh tìm kiếm phía trên → click Request Indexing

8.2 Social Sharing Previews

Khi bạn share link trên Twitter/X, LinkedIn, hay Facebook, chúng hiện preview card với title, description, và OG image của post. Verify chúng hoạt động:

Nhập URL blog hoặc URL post cụ thể. Bạn sẽ thấy:

  • Tiêu đề post
  • Mô tả
  • OG image (image tùy chỉnh bạn đã generate)

Nếu preview hiện sai, kiểm tra <meta> tags:

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

8.3 Analytics (Tùy Chọn)

Cân nhắc thêm analytics để track visitors. Các lựa chọn thân thiện privacy:

  • Plausible Analytics — nhẹ, privacy-first, không cookies (~$9/tháng)
  • Umami — self-hosted, open source, miễn phí
  • Google Analytics — miễn phí nhưng dùng cookies, cần consent banner ở EU

Cho approach self-hosted, Umami kết hợp tốt với Docker setup hiện tại — bạn có thể thêm nó như service khác trong docker-compose.yml.

8.4 robots.txt

Nếu chưa có, thêm robots.txt để hướng dẫn search engine crawlers:

Tạo public/robots.txt:

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

Cái này báo cho tất cả crawlers rằng chúng có thể index mọi thứ, và chỉ chúng đến sitemap.


9. Tùy Chọn: Cloudflare DNS Proxy

Nếu bạn muốn thêm một lớp bảo vệ, có thể đặt Cloudflare phía trước domain. Gói miễn phí của Cloudflare cho bạn:

  • DDoS protection — hấp thụ các cuộc tấn công volumetric
  • CDN caching — serve static assets từ edge servers toàn cầu
  • DNS analytics — xem query volume và response times
  • Always Online — hiện cached version nếu server bạn down

9.1 Setup Cloudflare

  1. Tạo tài khoản miễn phí tại cloudflare.com
  2. Thêm domain (chanh.blog)
  3. Cloudflare scan DNS records hiện tại
  4. Review và confirm records đã import khớp với cái bạn set trong Hostinger
  5. Cloudflare cung cấp hai nameservers (ví dụ: ada.ns.cloudflare.com, bob.ns.cloudflare.com)
  6. Trong Hostinger → Domains → DNS / Nameservers, đổi nameservers sang của Cloudflare
  7. Chờ nameserver propagation (có thể tới 24 giờ)

9.2 Cloudflare SSL Mode

Quan trọng: Set SSL mode của Cloudflare thành Full (Strict). Điều này đảm bảo:

  • Cloudflare ↔ Server bạn: encrypted bằng Let's Encrypt certificate
  • Browser ↔ Cloudflare: encrypted bằng certificate của Cloudflare

Không dùng "Flexible" SSL — nghĩa là Cloudflare kết nối server bạn qua HTTP thuần, phá bỏ mục đích có SSL trên VPS.

9.3 Khi Nào Nên Skip Cloudflare

Cloudflare thêm complexity. Cho blog cá nhân với traffic vừa phải, nó là optional. Bạn có thể skip nếu:

  • Muốn quản lý DNS đơn giản hơn (bớt một service phải cấu hình)
  • Không lo về DDoS attacks
  • Thích giữ infrastructure tối giản

VPS với Nginx + Let's Encrypt đã là setup khá solid rồi. Cloudflare là nice-to-have, không phải must-have.


Xử Lý Sự Cố

Domain không resolve

# Kiểm tra DNS records
dig chanh.blog +short
# Phải trả về VPS IP
 
# Nếu trống, DNS chưa propagate
# Kiểm tra trạng thái propagation tại whatsmydns.net

Nguyên nhân phổ biến:

  • DNS records chưa save đúng trong Hostinger
  • Nameserver change vẫn đang propagate (tới 48 giờ)
  • Gõ sai tên domain hoặc IP address

Certbot báo "Could not reach domain"

# Verify DNS trỏ về VPS
dig chanh.blog +short
 
# Verify port 80 mở
sudo ufw status
 
# Verify Nginx đang chạy và listening
sudo nginx -t
sudo systemctl status nginx
 
# Thử Certbot với verbose output
sudo certbot --nginx -d chanh.blog -d www.chanh.blog -v

Certbot cần truy cập http://chanh.blog/.well-known/acme-challenge/ để verify ownership. Nếu DNS chưa propagate hoặc port 80 bị block, nó sẽ fail.

www không redirect

Kiểm tra Nginx config có www server block với 301 redirect:

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

Nếu www block bị thiếu, sửa lại config và thêm vào (xem Phần 4.1).

OG images hiện domain cũ

Sau khi đổi NEXT_PUBLIC_SITE_URL, phải rebuild:

cd ~/my-blog
./deploy.sh

Sau đó xóa cache trên các nền tảng social:

  • Facebook: Dùng Sharing Debugger và click "Scrape Again"
  • LinkedIn: Dùng Post Inspector để refresh
  • Twitter/X: Cards tự update trong vài giờ

Tổng Kết

Trong phase cuối cùng này, bạn đã:

✅ Đăng ký custom domain trên Hostinger và cấu hình bảng quản lý DNS
✅ Thêm A records cho cả apex domain và www subdomain
✅ Verify DNS propagation bằng dig, nslookup, và online tools
✅ Cập nhật Nginx với tên miền và redirect www → apex
✅ Cấp SSL certificates cover cả chanh.blog và www.chanh.blog
✅ Cập nhật application config với NEXT_PUBLIC_SITE_URL mới
✅ Test end-to-end — HTTPS, redirects, posts, search, và feeds
✅ Submit lên Google Search Console với sitemap để indexing
✅ Verify social sharing previews trên Twitter/X, LinkedIn, và Facebook

Blog đã hoàn thành. Từ một thư mục trống đến blog self-hosted live trên custom domain — bạn đã tự tay build toàn bộ stack.


Series Hoàn Thành!

Chúc mừng! Bạn đã hoàn thành toàn bộ series Build a Personal Blog. Đây là mọi thứ bạn đã build qua 8 bài viết:

PhaseBạn Đã BuildBài Viết
OverviewRoadmap và kiến trúc seriesBuild a Personal Blog — Roadmap
Phase 1Setup project Next.js 16 + ShadCN/UIProject Setup
Phase 2MDX on-demand rendering pipelineMDX Rendering
Phase 3Tích hợp PostgreSQL + Drizzle ORMDatabase Layer
Phase 4Tags, search, và paginationBlog Features
Phase 5Docker Compose cho dev và productionDocker Compose
Phase 6Deploy lên Ubuntu VPS trên HostingerVPS Deployment
Phase 7Setup custom domain và launchBạn đang ở đây

Giờ bạn có skills để build, deploy, và maintain một production web application từ đầu. Cùng patterns này áp dụng cho bất kỳ project nào — không chỉ blogs. Docker, Nginx, SSL, DNS, và VPS management là skills DevOps nền tảng mà đi đâu cũng dùng được.


Series Index

BàiTiêu ĐềTrạng Thái
BLOG-1Build a Personal Blog — Roadmap✅ Hoàn thành
BLOG-2Phase 1: Project Setup — Next.js 16 + ShadCN/UI✅ Hoàn thành
BLOG-3Phase 2: MDX On-Demand Rendering✅ Hoàn thành
BLOG-4Phase 3: PostgreSQL + Drizzle ORM✅ Hoàn thành
BLOG-5Phase 4: Tags, Search & Pagination✅ Hoàn thành
BLOG-6Phase 5: Docker Compose✅ Hoàn thành
BLOG-7Phase 6: Deploy lên Ubuntu VPS trên Hostinger✅ Hoàn thành
BLOG-8Phase 7: Setup Custom Domain trên Hostinger✅ Bạn đang ở đây

📬 Đăng ký nhận bản tin

Nhận những bài viết mới nhất qua email mỗi tuần. Không spam, hủy đăng ký bất cứ lúc nào.

Chúng tôi tôn trọng quyền riêng tư của bạn. Hủy đăng ký bất cứ lúc nào.

💬 Comments

Đăng nhập để bình luận

Chúng tôi sẽ không đăng bất cứ điều gì mà không có sự cho phép của bạn.