Module 0 · Bài 00

Bản đồ toàn cảnh: một request đi qua chín chặng

Từ lúc gõ một địa chỉ vào trình duyệt tới lúc code Spring Boot của bạn chạy, request đi qua chín chặng. Bài này vẽ tấm bản đồ đó và chỉ ra chặng nào là code của bạn.

Đã xong 3-5 giờ học Lab: Git Bash Đọc output thật Phụ lục: lịch sử DevOps

Web không vào được: tìm lỗi ở đâu?

Tình huống

Giả sử sếp nhắn đúng một câu: "Web không vào được em ơi." Không có ảnh chụp, không có mã lỗi, không biết hỏng từ lúc nào.

Nhận được tin như thế thì bắt đầu tìm từ đâu? Chỗ dễ nghĩ tới nhất là code. Mở IDE ra đọc lại là phản xạ rất tự nhiên, vì code là thứ bạn viết, bạn hiểu từng dòng và sửa được ngay.

Có điều, giả sử từ hôm qua tới giờ code không đổi dòng nào, mà hôm qua web vẫn chạy. Một hệ thống đang chạy tốt thì chỉ hỏng khi có thứ gì đó thay đổi. Code đã không đổi, vậy thứ đã đổi nằm ở đâu?

Nó nằm ở hạ tầng (infrastructure). Hạ tầng là tên chung cho mọi thứ đứng giữa người dùng và code của bạn: đường mạng, các máy chủ (server), và những chương trình chạy trước hoặc sau ứng dụng.

Muốn tìm lỗi trong hạ tầng thì trước hết phải biết một lượt truy cập đi qua hạ tầng như thế nào. Mỗi lần người dùng mở một trang, trình duyệt gửi đi một request, là một thông điệp xin một thứ gì đó, ví dụ "cho tôi dữ liệu của người dùng số 42". Code xử lý request thì bạn viết hằng ngày rồi. Bài này nhìn từ phía ngược lại: đi theo request từ lúc nó sinh ra trên máy người dùng.

Từ máy người dùng, request phải đi qua nhiều bước mới tới được controller của bạn, và mỗi bước như vậy mình gọi là một chặng. Chưa biết các chặng đó thì lúc có sự cố chỉ còn cách tìm ở chỗ quen, là code. Thứ đang thiếu vì thế là một tấm bản đồ của cả đường đi, và bài này vẽ tấm bản đồ đó.

  người dùng gõ                               code bạn viết
  https://api.example.com/users/42            @GetMapping("/users/{id}")
                 │                                       ▲
                 └──────→   ?   ?   ?   ?   ?   ──────────┘
                            những chặng ở giữa:
                            bài này vẽ chúng ra
Hình 1 Hai đầu bạn đã biết, còn khoảng giữa là chỗ bài này lấp vào.
Chữ DevOps trong tên khoá học

Tên khoá học có chữ DevOps, vậy chữ đó dính gì tới tấm bản đồ này? DevOps ghép từ development (phát triển: viết phần mềm) và operations (vận hành: giữ cho phần mềm chạy). Muốn làm chung hai việc đó thì phải nhìn được trọn khoảng giữa ở Hình 1, nghĩa là hiểu cả code lẫn những thứ quanh nó.

Còn định nghĩa đầy đủ theo chuẩn quốc tế thì mình để ở tab Phía server, vì tới lúc ấy bản đồ đã vẽ xong và bạn thấy rõ phần nào không phải code của bạn.

Câu hỏi của bài

Từ lúc gõ một địa chỉ vào trình duyệt tới lúc code Spring Boot của bạn chạy, request đi qua những chặng nào, và chặng nào thật sự là code của bạn?

Câu hỏi này lớn, nên bài chia nó thành ba câu nhỏ và trả lời lần lượt:

  1. Một URL nói gì? Phần nào dành cho hạ tầng, phần nào dành cho code?
  2. Request đi qua những chặng nào? Chặng nào chạy trên máy người dùng, chặng nào chạy trên server?
  3. Chặng nào là code của bạn? Điều đó cho biết gì về chỗ cần tìm khi có sự cố, và ai lo phần còn lại?

Cần đã học trước

Bài này cần bạn biết trước những gì? Vì là bài đầu nên chỉ cần một thứ: viết và chạy được một ứng dụng Spring Boot trên máy. Nói rõ hơn là viết được một controller có @GetMapping, chạy ứng dụng, rồi mở http://localhost:8080 trong trình duyệt để xem kết quả.

Mình bắt đầu khoá này cũng với đúng chừng đó: viết Spring Boot hằng ngày mà chưa biết gì về những thứ nằm ngoài code. Vì vậy thuật ngữ hạ tầng nào cũng được giải nghĩa ngay ở chỗ nó xuất hiện lần đầu.

Tấm bản đồ nên bắt đầu vẽ từ đâu? Từ chính chuỗi chữ người dùng gõ vào thanh địa chỉ, vì request nào cũng sinh ra từ đó.

Hai đầu của một request: client và server

Request nào cũng có hai bên. Bên gửi là chương trình người dùng đang dùng, còn bên nhận và trả lời là một chương trình chạy trên một máy ở xa. Hai vai này có tên chuẩn, và từ đây bài gọi chúng bằng đúng hai tên đó.

Client máy khách
Chương trình gửi request đi và chờ câu trả lời. Trình duyệt là client; lệnh curl bạn sẽ chạy ở tab Lab cũng là client.
Server máy chủ
Chương trình nhận request và gửi câu trả lời (response). Chữ "server" còn dùng để chỉ cái máy chạy chương trình đó; trong bài này nghĩa nào đang dùng thì nhìn ngữ cảnh là biết.

Đọc cho đúng một URL

Chuỗi người dùng gõ vào có tên là URL (Uniform Resource Locator, địa chỉ tài nguyên). Nhìn thì URL như một khối liền, nhưng client đọc nó thành năm phần, và mỗi phần lại do một bộ phận khác nhau lo. Vì sao phải tách kỹ như vậy? Vì biết ai lo phần nào thì mới biết một lỗi thuộc về ai.

  https://api.example.com:443/users/42?active=true
  └─┬─┘   └──────┬───────┘ └┬┘└───┬──┘└─────┬────┘
  scheme       host       port  path      query
Hình 2 Năm phần của một URL. Chỉ path và query là thứ code Spring Boot của bạn thực sự đọc tới.
scheme giao thức
Phần đứng trước ://, cho biết client sẽ nói chuyện với server bằng giao thức nào: http (không mã hoá) hay https (có mã hoá).
host tên máy chủ
Một cái tên cho người đọc, như api.example.com. Cái tên này còn gọi là tên miền (domain name).
port cổng
Một con số chỉ ra chương trình nào trên máy đích sẽ nhận request. Một máy có thể chạy nhiều chương trình cùng nhận request, mỗi chương trình một port riêng.
path đường dẫn
Phần chỉ ra tài nguyên bên trong ứng dụng, như /users/42. Đây là phần @GetMapping của bạn khớp vào.
query chuỗi truy vấn
Phần sau dấu ?, gồm các cặp khoá và giá trị như active=true. Trong Spring Boot, đây là thứ @RequestParam đọc ra.
Sơ đồ chữ trong văn bản chuẩn RFC 3986 tách địa chỉ foo://example.com:8042/over/there?name=ferret#nose thành scheme, authority, path, query và fragment.
Ảnh 1 Cách tách một địa chỉ ngay trong văn bản chuẩn định nghĩa nó, RFC 3986. RFC là loạt văn bản chuẩn kỹ thuật của Internet, mỗi văn bản một số hiệu riêng. Chuẩn gộp host và port thành một phần tên là authority, và có thêm phần fragment sau dấu #. Fragment là phần duy nhất trình duyệt không bao giờ gửi lên server: nó chỉ dùng ở phía trình duyệt, ví dụ để nhảy tới một mục trong trang.

Trong năm phần đó có ba chỗ đáng dừng lại, vì chỗ nào cũng dẫn tới một chặng trên bản đồ ở tab sau.

Host là một cái tên, còn mạng cần một địa chỉ

Người dùng gõ một cái tên như api.example.com, vậy mạng có chuyển dữ liệu tới cái tên đó được không? Không. Mạng chỉ chuyển dữ liệu tới một địa chỉ IP, là con số định danh một máy trên mạng. Loại địa chỉ phổ biến nhất viết thành bốn số, ví dụ 104.20.23.154, một trong các địa chỉ của example.com mà bạn sẽ thấy ở tab Lab.

Nói cho chính xác thì địa chỉ IP gắn với từng card mạng của máy (card WiFi chẳng hạn) chứ không gắn với cả máy. Ở mức bản đồ thì cứ coi mỗi máy có một địa chỉ là đủ.

Người gõ tên mà mạng lại cần số, nên giữa lúc người dùng gõ xong và lúc client gửi được dữ liệu đầu tiên luôn có một bước dịch tên thành địa chỉ. Bước dịch đó do một hệ thống riêng lo, gọi là DNS (Domain Name System, hệ thống tên miền). DNS là một mạng lưới server trải khắp thế giới, chuyên trả lời câu hỏi "tên này ứng với địa chỉ nào".

Điều đáng nhớ về DNS là nó có thể hỏng riêng một mình, không dính gì tới phần còn lại. Quên gia hạn tên miền, hay nhà cung cấp DNS gặp sự cố, đều làm web "không vào được" dù server và code vẫn khoẻ.

Scheme quyết định port mặc định

URL ở Hình 2 có ghi port, nhưng phần lớn địa chỉ bạn gõ hằng ngày thì không. Vậy lúc đó client lấy port ở đâu ra? Nó tự điền theo scheme: http:// đi với port 80, còn https:// đi với port 443. Quy ước này nằm trong chuẩn HTTP (RFC 9110).

Nhờ quy ước đó mà vào website công cộng bạn không phải gõ port. Còn khi chạy Spring Boot trên máy mình thì phải gõ :8080, vì ứng dụng nhận request ở một port không mặc định và trình duyệt không đoán được.

Thế trên một server thật thì ai đứng ở port 443, và vì sao người dùng không bao giờ phải biết con số 8080? Hai câu này để tab Phía server trả lời.

Path và query là phần hạ tầng không đọc

Còn /users/42 thì ai hiểu nó là "người dùng số 42"? Không thành phần hạ tầng nào cả. DNS chỉ cần host, còn phần lớn chương trình trung gian thì chuyển nguyên path vào trong. Chỉ ứng dụng, tức code bạn viết, mới gán ý nghĩa cho path và query.

Vậy là URL chia làm hai nửa. Ba phần đầu (scheme, host, port) là việc của hạ tầng, vì chúng quyết định request có được mã hoá không, đi tới máy nào, và vào chương trình nào trên máy đó. Hai phần cuối mới là việc của bạn.

Nối với thứ bạn đã biết

Khi viết @GetMapping("/users/{id}"), thứ bạn khai báo chỉ là phần path. Ba phần đứng trước, cùng việc tìm ra và kết nối tới đúng máy, đã được xử lý xong ở đâu đó trước khi request chạm tới controller của bạn.

Từ lúc trình duyệt tách xong URL tới lúc controller của bạn chạy, request ghé qua những đâu? Hình 4 trả lời ở mức toàn cảnh. Đó là hình quan trọng nhất của bài, vì mỗi thành phần hạ tầng đều nằm ở một chặng trên đó.

Vì sao trên máy bạn chỉ thấy vài bước, còn trên server thật có chín

Trước khi xem cả chín chặng, thử nhìn lại đường đi quen thuộc lúc bạn đang code. Khi đó mọi thứ nằm trên một máy nên đường đi rất ngắn: trình duyệt gọi thẳng localhost:8080, Spring Boot nhận request, đọc database rồi trả lời. Trên đường đó không có tên miền nào để dịch, không ai mã hoá, và cũng không ai chặn ở giữa.

  trình duyệt ──→ localhost:8080 ──→ Spring Boot ──→ PostgreSQL
                  cùng một máy, không qua mạng thật
Hình 3 Đường đi của request khi bạn đang code trên máy mình.

Vậy khi đưa ứng dụng lên server để người dùng thật vào qua Internet thì có gì khác? Đường đi xa hơn, và môi trường không còn an toàn như trên máy bạn nữa. Vì hai chuyện đó mà phải thêm bốn thành phần, mỗi thành phần giải quyết đúng một vấn đề mà trên máy bạn chưa gặp (Bảng 1).

Bảng 1 Bốn thành phần xuất hiện khi ứng dụng đi từ máy bạn lên Internet.
Thành phầnVì sao phải có
DNSNgười dùng chỉ biết cái tên api.example.com, không biết địa chỉ IP của server.
TLS, lớp mã hoá của httpsDữ liệu đi qua Internet phải được mã hoá, và trình duyệt phải kiểm chứng được máy bên kia đúng là api.example.com.
Firewall (tường lửa)Server giờ phơi ra toàn bộ Internet, nơi các chương trình tự động dò quét liên tục; phải có một lớp chặn những gì không được phép vào.
NginxPhải có một chương trình đứng trước ứng dụng: nhận request ở port 443, gỡ lớp mã hoá, rồi chuyển request vào trong.

Tấm bản đồ chín chặng

Ghép bốn thành phần đó với những gì đã có thì ra tấm bản đồ đầy đủ ở Hình 4. Đọc từ trên xuống theo đúng thứ tự request đi qua: nửa trên chạy trên máy người dùng, nửa dưới chạy trên server, ở giữa là Internet.

  ┌─────────────────── máy người dùng ──────────────────────┐
  │  Trình duyệt     tách URL: scheme, host, port, path     │
  │  DNS             hỏi "api.example.com là địa chỉ nào?"  │
  │                  được trả lời: 203.0.113.10             │
  │  TCP             mở kết nối tới 203.0.113.10, port 443  │
  │  TLS             kiểm tra chứng chỉ, bật mã hoá         │
  │  HTTP            gửi "GET /users/42" kèm các header     │
  └───────────────────────────┬─────────────────────────────┘
                              │
                     ~~~ Internet ~~~
                              │
  ┌──────────── server (một máy chạy suốt ngày đêm) ────────┐
  │  Firewall        dữ liệu tới port 443 có được vào?      │
  │  Nginx           nhận ở port 443, gỡ mã hoá, đọc Host,  │
  │                  chuyển request vào ứng dụng            │
  │  Spring Boot     nhận ở port 8080, chạy code của bạn    │
  │  PostgreSQL      nhận ở port 5432, đọc và ghi dữ liệu   │
  └─────────────────────────────────────────────────────────┘
Hình 4 Bản đồ chín chặng của một request HTTPS, đọc từ trên xuống theo đúng thứ tự request đi qua. Năm chặng trong khung trên chạy trên máy người dùng, bốn chặng trong khung dưới chạy trên server. Địa chỉ 203.0.113.10 thuộc một dải dành riêng cho ví dụ trong tài liệu.

Hình 4 có tới chín cái tên. Có cần hiểu hết ngay không? Chưa cần. Mỗi chặng có một thành phần lo và một kiểu hỏng riêng. Lúc này chỉ cần Bảng 2: nó tóm mỗi chặng bằng một câu để các tên trong Hình 4 có nghĩa ngay từ bây giờ, còn hai tab sau giải thích thêm vừa đủ để hiểu bản đồ.

Bảng 2 Chín chặng theo thứ tự request đi qua, mỗi chặng một câu.
Thành phầnChạy ở đâuLàm gì
Trình duyệtmáy người dùngTách URL thành các phần, điền port mặc định nếu thiếu.
DNSmáy người dùng hỏi, server DNS trả lờiDịch tên miền thành địa chỉ IP.
TCPmáy người dùng và serverMở một kết nối tin cậy tới đúng địa chỉ IP và port.
TLSmáy người dùng và serverKiểm tra giấy tờ điện tử chứng minh server đúng là tên miền nó nhận (gọi là chứng chỉ), rồi mã hoá mọi thứ gửi sau đó.
HTTPmáy người dùngGửi request: phương thức, path, các header.
FirewallserverCho phép hoặc chặn dữ liệu đi vào theo luật.
NginxserverNhận request thay ứng dụng, gỡ mã hoá, chuyển vào trong.
Spring BootserverChạy code của bạn.
PostgreSQLserverLưu và trả dữ liệu cho ứng dụng.

Vì sao thứ tự là TCP, rồi TLS, rồi HTTP

Trong Hình 4, ba việc mở kết nối TCP, bắt tay TLS và gửi request HTTP đứng liền nhau, và lúc nào cũng theo đúng thứ tự đó. Vì sao không đảo được? Vì bước sau chạy ở bên trong bước trước.

TCP phải mở xong trước, vì TLS là một cuộc trao đổi diễn ra bên trong chính kết nối đó. Rồi TLS phải bật mã hoá xong thì trình duyệt mới gửi dòng GET /users/42. Nhờ thứ tự này mà path và các header của request, kể cả cookie đăng nhập, đều đi qua Internet ở dạng đã mã hoá.

Cách tổ chức như vậy có tên là phân tầng (layering): mỗi tầng chỉ dựa vào tầng ngay bên dưới nó. Phân tầng thì được lợi ở chỗ sửa hay thay một tầng không phải viết lại các tầng khác.

Đó cũng là lý do TCP, TLS và HTTP có ba văn bản chuẩn riêng, viết độc lập với nhau. Số hiệu từng văn bản nằm ở mục Nguồn đọc thêm cuối bài, cùng với văn bản chuẩn của DNS.

Năm chặng trong khung trên của Hình 4 chạy ngay trên máy người dùng, lúc chưa có byte nào của request tới được server. Phần dưới đây đi chậm qua từng chặng đó. Đi kỹ tới đâu? Vừa đủ để gặp chặng nào hỏng thì bạn nhận ra.

Trình duyệt tách URL

Người dùng gõ xong địa chỉ và bấm Enter thì trình duyệt làm gì trước tiên? Nó tách URL thành năm phần như ở tab URL, rồi điền port mặc định nếu URL không ghi. Lấy https://api.example.com/users/42 làm ví dụ. Tách xong URL này thì trình duyệt biết bốn điều: phải dùng HTTPS, phải tìm địa chỉ của api.example.com, kết nối tới port 443, và xin path /users/42.

Bước tách này hiếm khi hỏng, trừ khi chính URL bị gõ sai.

DNS dịch tên thành địa chỉ

Trình duyệt đã biết phải tìm địa chỉ của api.example.com, nhưng tìm ở đâu? Máy người dùng không tự biết câu trả lời, nên nó phải hỏi DNS. Hệ điều hành gửi câu hỏi tới một server DNS đã được cài sẵn cho nó, thường là cục phát WiFi trong nhà hoặc một server của nhà mạng. Server DNS đó hoặc trả lời luôn, hoặc đi hỏi tiếp giúp rồi mới trả lời.

Câu trả lời mà server DNS đưa ra thì rốt cuộc đến từ chủ tên miền. Họ khai sẵn các bản ghi DNS, mỗi bản ghi là một dòng nói rằng tên này ứng với địa chỉ này, và câu trả lời gốc nằm ở những dòng đó.

Bạn cũng tự hỏi DNS được, bằng lệnh nslookup có sẵn trên cả Windows lẫn Linux. Dưới đây là output thật khi mình chạy Lab 1 trên máy mình ngày 24/09/2026, trong Git Bash trên Windows (tên router nhà mình đã được ẩn):

$ nslookup github.com
Server:  (tên router, đã ẩn)
Address: fe80::10

Non-authoritative answer:
Name:    github.com
Address: 20.205.243.166
Hình 5 Hỏi DNS địa chỉ của github.com. Khối trên cho biết ai đã trả lời: router trong nhà. Địa chỉ fe80::… của nó là loại chỉ dùng được trong mạng nội bộ. Khối dưới cho biết câu trả lời là gì.

Còn nếu hỏi một tên miền không tồn tại thì sao? Server DNS nói đúng như vậy, và đường đi của request dừng ngay ở bước hỏi DNS:

$ nslookup khong-ton-tai-dau-nhe-12345.com
*** (tên router, đã ẩn) can't find khong-ton-tai-dau-nhe-12345.com: Non-existent domain
Hình 6 Bước hỏi DNS hỏng. Mã trả lời này có tên là NXDOMAIN: tên miền không tồn tại.

Chỗ đáng để ý ở Hình 6 là những thứ chưa xảy ra. Khi DNS trả lời "không có tên này" thì client chưa có địa chỉ nào để kết nối, nên nó chưa mở kết nối, chưa bắt tay TLS, và chưa gửi request. Vì vậy server, Nginx và code của bạn đều không hề biết có người vừa thử vào.

Trang Đọc output thật giải thích kỹ từng dòng của hai output này.

TCP mở kết nối

Có địa chỉ rồi thì client bắt đầu gửi dữ liệu. Nhưng dữ liệu đi trên mạng dưới dạng gì? Nó không đi thành một khối, mà bị cắt thành những mẩu nhỏ gọi là gói tin (packet). Mỗi gói tin có phần đầu ghi địa chỉ nơi gửi và nơi nhận, giống cách mỗi request HTTP mang các header.

Gói tin cũng không bay thẳng một mạch tới server. Trên đường đi nó ghé qua nhiều router, là thiết bị nối các mạng với nhau: nhận gói tin từ mạng này rồi chuyển sang mạng gần đích hơn. Router đầu tiên chính là cục phát WiFi trong nhà người dùng, sau đó tới router của nhà mạng, rồi router của các mạng trung gian, cho tới mạng nơi đặt server.

Một router không dây gia đình màu đen có hai ăng-ten dựng đứng.
Ảnh 2 Một router gia đình. Mọi gói tin rời máy người dùng để ra Internet đều đi qua thiết bị này đầu tiên. Ở nhiều mạng gia đình, nó cũng chính là server DNS đã trả lời ở Hình 5.

Đi rời rạc qua nhiều router như vậy thì gói tin có thể mất, tới sai thứ tự, hoặc tới hai lần, và TCP (Transmission Control Protocol) sinh ra để lo đúng chuyện đó.

Cách TCP lo là thế này. Trước khi gửi dữ liệu, hai máy trao đổi vài gói tin để thoả thuận mở một kết nối. Kể từ lúc có kết nối, TCP đánh số mọi gói gửi đi, gửi lại gói nào bị mất, và xếp các gói lại cho đúng thứ tự. Một kết nối luôn mở tới một địa chỉ IP và một port cụ thể, ở đây là 203.0.113.10 và port 443.

Khi bạn đưa ứng dụng cho người khác dùng thì mở kết nối thường là chỗ hỏng đầu tiên, và nó hỏng theo hai kiểu rất khác nhau: bị từ chối ngay, hoặc chờ mãi không ai trả lời.

TLS mã hoá và chứng minh danh tính

Kết nối đã mở, nhưng dữ liệu chạy trong nó vẫn đi qua router của nhiều mạng khác. Làm sao để không ai đứng giữa đọc được? Đó là việc của chữ s trong https: nó cho biết dữ liệu đi qua một lớp bảo mật tên là TLS (Transport Layer Security). TLS làm hai việc.

Việc thứ nhất là chứng minh danh tính: bảo đảm máy đang trả lời đúng là api.example.com chứ không phải một máy mạo danh. Việc thứ hai là mã hoá: biến mọi dữ liệu gửi sau đó thành dạng mà người đứng giữa không đọc được.

Vì sao chứng minh danh tính lại phải đi trước? Vì nếu máy ở đầu kia là máy mạo danh thì mã hoá kỹ tới đâu, dữ liệu vẫn tới đúng tay máy mạo danh đó.

Truyện ở Ảnh 3 nói về việc thứ hai, mã hoá, bằng một tình huống ngoài đời.

Truyện tranh một khung. Một người đứng trước quầy nói: Chào buổi sáng, tôi muốn xem các đơn hàng mới nhất của cửa hàng. Người bán đáp: Anh điên à, đó là thông tin mật. Người kia nói: Xin lỗi, vì trang web của anh không dùng SSL nên tôi tưởng anh không quan tâm tới bí mật.
Ảnh 3 Truyện We need to talk about SSL của Daniel Stori, turnoff.us, năm 2017. Một người lạ hỏi xem đơn hàng của cửa hàng, và chủ cửa hàng bảo đó là thông tin mật. Người kia đáp rằng trang web của cửa hàng không dùng SSL, nên cứ tưởng cửa hàng không quan tâm tới bí mật. SSL là giao thức đời trước của TLS, và nhiều người vẫn gọi TLS bằng tên ấy. Truyện đúng ở chỗ: không có lớp mã hoá thì mọi máy đứng giữa đường đi đều đọc được dữ liệu.

Giờ quay lại việc thứ nhất. Để chứng minh danh tính, server trình ra một chứng chỉ (certificate), là một tài liệu điện tử ghi nó thuộc về tên miền nào và còn hiệu lực tới khi nào.

Lúc học tới đây mình hỏi ngay: chứng chỉ có phải là chỗ cấp phép đăng ký tên miền với địa chỉ IP không? Không phải. Gắn tên với địa chỉ là việc của DNS ở trên. Chứng chỉ chỉ chứng minh máy đang trả lời có quyền dùng cái tên đó, và trong nó không có địa chỉ IP nào.

Nhưng một tài liệu như thế thì ai cũng tự viết ra được, vậy sao trình duyệt lại tin? Vì chứng chỉ chỉ được tin khi có một CA (Certificate Authority, tổ chức cấp chứng chỉ) ký vào, mà CA là những tổ chức trình duyệt và hệ điều hành đã tin sẵn từ lúc được cài. Trình duyệt kiểm tra chữ ký của CA, tên miền và hạn dùng, rồi mới đi tiếp.

Lab 3 đọc chứng chỉ thật của example.com bằng lệnh openssl. Đây là kết quả khi mình chạy nó ngày 24/09/2026:

subject=CN=example.com
issuer=C=US, O=SSL Corporation, CN=Cloudflare TLS Issuing ECC CA 3
notBefore=Jul 29 22:10:08 2026 GMT
notAfter=Oct 27 22:17:21 2026 GMT
Hình 7 Chứng chỉ của example.com, đọc ngày 24/09/2026: cấp cho ai (subject), ai ký (issuer), hiệu lực từ ngày nào tới ngày nào.

Hai dòng cuối cho thấy chứng chỉ này chỉ có hiệu lực 90 ngày, từ 29/07 tới 27/10. Chứng chỉ đã có hạn dùng thì một website có thể hỏng ở bước bắt tay TLS đúng vào ngày chứng chỉ hết hạn, dù không ai đụng vào gì.

Có lần mình đoán thế này: một website hết chứng chỉ đúng thứ Hai là do ai đó đăng ký nó vào một hôm nào trong quá khứ, rồi tình cờ trùng. Không tình cờ đâu. Hạn là ngày cấp cộng 90 ngày, nên chứng chỉ hết vào một ngày biết trước được. Tới ngày đó mà chưa ai gia hạn thì web hỏng.

Gửi request HTTP

Tới đây DNS đã trả lời, kết nối TCP đã mở, TLS đã bật mã hoá, và lúc này trình duyệt mới gửi request.

Request đó được viết theo HTTP (Hypertext Transfer Protocol), là quy ước về cách viết request và response. Theo quy ước này, một request là một đoạn chữ có cấu trúc: dòng đầu gồm phương thức (GET, POST…), path và phiên bản giao thức, sau đó là các header, mỗi dòng một cặp tên và giá trị. Đây là request thật mà curl gửi đi ở Lab 2:

> GET / HTTP/1.1
> Host: example.com
> User-Agent: curl/8.21.0
> Accept: */*
Hình 8 Một request HTTP, nguyên văn. Ký tự > đầu dòng là cách curl -v đánh dấu dữ liệu gửi đi.

Trong Hình 8 có dòng Host: example.com. Kết nối đã mở tới đúng máy rồi, sao request còn phải ghi lại tên miền? Vì một server có thể phục vụ nhiều website cùng lúc trên cùng một địa chỉ IP, nên nó cần header Host để biết request này dành cho website nào. Ở tab sau bạn sẽ thấy Nginx dùng đúng dòng này.

Request đã rời máy người dùng. Từ đây nó còn phải qua những đâu mới chạm được code của bạn? Lần lượt là Internet, firewall, Nginx, rồi mới tới ứng dụng và database.

Internet: phần bạn không kiểm soát

Dòng ~~~ Internet ~~~ trong Hình 4 là chuỗi router và đường truyền nằm giữa mạng của người dùng và mạng của server. Ở đoạn này bạn gần như không làm được gì, vì bạn không sở hữu mà cũng không cấu hình được thứ nào ở đó. Thứ bạn quyết định được chỉ là hai đầu: request gửi đi đúng cách, và server ở đầu kia sẵn sàng nhận.

Bốn mẫu cáp quang biển cắt ngang đặt cạnh nhau, có nhãn chú thích từng lớp: lõi sợi quang, dây đồng cấp điện, lớp giáp thép.
Ảnh 4 Phần Internet giữa các châu lục có hình hài vật lý thật: phần lớn lưu lượng đi qua cáp quang biển. Đây là mẫu cắt ngang bốn loại vỏ cáp. Sợi quang nằm ở lõi, bọc quanh nó là dây đồng cấp điện cho các bộ khuếch đại dọc tuyến, còn ngoài cùng là các lớp giáp thép. Giáp dày dần ở những đoạn gần bờ, vì ở đó cáp dễ bị neo tàu kéo đứt.

Firewall cho qua hoặc chặn

Qua hết Internet, gói tin đầu tiên tới server thì gặp ai trước? Nó gặp firewall (tường lửa), một bộ lọc chạy trên server hoặc ngay trước server. Firewall xem từng gói tin đi vào rồi quyết định cho qua hay chặn, theo các luật kiểu "chỉ cho phép kết nối tới port 443".

Vì firewall đứng trước mọi chương trình trên server, gói tin nào đã bị nó chặn thì không chương trình nào thấy được. Còn lý do phải có firewall thì Bảng 1 đã nêu: server phơi ra Internet, mà ở đó luôn có chương trình tự động dò xem port nào đang mở.

Nginx nhận request thay ứng dụng

Gói tin được firewall cho qua thì tới port 443. Chương trình đang nhận kết nối ở port đó có phải ứng dụng của bạn không? Chưa phải. Nó là một reverse proxy, tức chương trình đứng trước ứng dụng và nhận request từ bên ngoài thay cho ứng dụng.

Nhận request xong, reverse proxy tự mở một request khác vào ứng dụng ở bên trong, rồi chuyển câu trả lời ngược ra. Vì vậy người dùng chỉ nói chuyện với reverse proxy, không bao giờ kết nối thẳng tới ứng dụng.

Trong lộ trình này reverse proxy là Nginx, một chương trình mã nguồn mở rất phổ biến cho đúng việc đó. Với mỗi request, Nginx làm ba việc: gỡ lớp mã hoá TLS, đọc header Host để biết request dành cho website nào, rồi chuyển request vào ứng dụng đang chạy ở port 8080.

Tới đây thì trả lời được câu hỏi để lại ở tab URL: Nginx là bên đứng ở port 443, nên người dùng không cần biết có con số 8080.

Trang trắng với tiêu đề lớn Welcome to nginx! và vài dòng hướng dẫn, kết thúc bằng Thank you for using nginx.
Ảnh 5 Trang chào mặc định của Nginx, chụp từ Nginx 1.30.5 mình cài và chạy thật trên máy mình. Vừa cài xong, Nginx đã nhận request và tự trả lời bằng một trang có sẵn, chưa chuyển gì vào ứng dụng nào. Nên Nginx tự trả lời được một số request mà không cần tới ứng dụng.

Spring Boot tự nhận request được, vậy đặt thêm một reverse proxy phía trước để làm gì? Ở mức bản đồ thì có ba lý do, và cả ba đều đã xuất hiện trong bài. Nó lo phần mã hoá thay cho ứng dụng. Nó đọc header Host nên nhiều website dùng chung được một địa chỉ IP. Và nó là thứ duy nhất nhận kết nối từ Internet, nên ứng dụng được giấu ở phía sau.

Ứng dụng của bạn và database

Sau Nginx mới tới ứng dụng Spring Boot. Request vào ứng dụng qua một web server nhúng nằm ngay bên trong nó, mặc định là Tomcat. Tomcat nhận request ở port 8080, tìm controller có path khớp, rồi chạy code của bạn.

Trên server thật, ứng dụng thường được cấu hình để chỉ nhận kết nối từ chính máy đó. Nhờ vậy chỉ Nginx gọi vào được, còn từ Internet thì không ai chạm thẳng tới port 8080.

Cuối đường đi là database, ở đây là PostgreSQL, nhận kết nối ở port 5432. Code của bạn mở kết nối tới nó để đọc và ghi dữ liệu, rồi dùng kết quả để viết response. Database cũng không nhận kết nối nào từ Internet, nó chỉ nhận từ ứng dụng.

Có response rồi thì nó đi ngược lại đúng con đường cũ để về tới người dùng: từ Spring Boot ra Nginx, qua Internet, về trình duyệt.

Chặng nào là code của bạn

Đi hết chín chặng rồi, giờ quay lại câu hỏi của bài: chặng nào là code của bạn? Trong cả đường đi, chỉ ứng dụng là code bạn viết. Tám chặng còn lại là hạ tầng: chương trình của người khác, cấu hình của người vận hành, hoặc mạng của các nhà cung cấp. Chặng nào trong số đó cũng có thể hỏng trong khi code của bạn không đổi một dòng.

Vì thế khi nhận tin "web không vào được" mà code không đổi từ hôm qua thì hãy nghi tám chặng hạ tầng trước, còn phần code quen tay thì xem sau.

Truyện ở Ảnh 6 vẽ cùng ý đó: phần của bạn nằm trên rất nhiều phần của người khác.

Một chồng khối xếp cao, chú thích All modern digital infrastructure. Gần đáy có một khối nhỏ xíu đỡ cả chồng, mũi tên chỉ vào ghi: A project some random person in Nebraska has been thanklessly maintaining since 2003.
Ảnh 6 Truyện Dependency của xkcd, số 2347, năm 2020. Cả khối "hạ tầng số hiện đại" đứng trên một mảnh nhỏ do một người nào đó lặng lẽ duy trì từ năm 2003. Truyện nói quá, nhưng trùng với kết luận của mục này: phần lớn những gì request đi qua là chương trình của người khác, và code của bạn dựa vào chúng.

DevOps: ai lo cho tám chặng còn lại

Tám chặng kia không phải code của bạn, vậy ai lo chúng? Một thời gian dài, câu trả lời là một đội khác.

Đội phát triển (development, gọi tắt là dev) viết ứng dụng, còn đội vận hành (operations, gọi tắt là ops) giữ server, mạng, firewall, Nginx và database. Dev viết xong thì bàn giao, ops nhận về rồi đưa lên server.

Chia việc như vậy thì yếu ở đâu? Ở đúng lúc có sự cố. Dev chỉ thấy code của mình nên kết luận "lỗi ở server", ops thấy server vẫn chạy nên kết luận "lỗi ở code", và không ai nhìn được cả chín chặng cùng lúc.

Định nghĩa trong chuẩn

DevOps là tập hợp các nguyên tắc và cách làm giúp các bên liên quan giao tiếp và cộng tác tốt hơn, nhằm đặc tả, phát triển và vận hành các sản phẩm và dịch vụ phần mềm và hệ thống, đồng thời cải tiến liên tục mọi khía cạnh trong vòng đời của chúng.

Đây là định nghĩa ở mục 3.1 của chuẩn IEEE 2675-2021 (IEEE Standard for DevOps), về sau được chấp nhận thành chuẩn quốc tế ISO/IEC/IEEE 32675:2022. Nguyên văn: "DevOps: Set of principles and practices which enable better communication and collaboration between relevant stakeholders for the purpose of specifying, developing, and operating software and systems products and services, and continuous improvements in all aspects of the life cycle."

Cái tên DevOps thì có trước chuẩn hơn mười năm: nó lan ra từ hội nghị devopsdays đầu tiên, tổ chức ở Ghent, Bỉ, năm 2009. Còn vì sao hai đội từng tách ra, vì sao cách làm đó hết dùng được, và ngày nay người ta đo DevOps bằng gì thì mình kể trong phụ lục Lịch sử DevOps.

Định nghĩa trong chuẩn khá trừu tượng, vậy đặt lên tấm bản đồ thì nó nói gì? Các bên liên quan (stakeholders) là những người có phần trong việc phần mềm được làm ra và chạy ra sao. Trên bản đồ, trước hết đó là hai nhóm: người viết ứng dụng, và người giữ cho phần còn lại của đường đi chạy được (server, firewall, Nginx, database).

Còn "giao tiếp và cộng tác tốt hơn" thì hằng ngày trông ra sao? Thường nó hiện ra thành ba việc:

  • Người viết ứng dụng hiểu hạ tầng mà ứng dụng chạy trên đó, và cùng chịu trách nhiệm cho nó.
  • Những bước lặp đi lặp lại giữa code và server được tự động hoá.
  • Hệ thống được đo đạc để biết nó đang tốt lên hay xấu đi. Số đo đó là căn cứ cho việc cải tiến liên tục mà định nghĩa nhắc tới.

Ba việc này là cách bài này diễn giải, không trích từ chuẩn.

Chuẩn không dừng ở định nghĩa. Mục 5.2 của nó liệt kê các nguyên tắc của DevOps, và có một nguyên tắc gắn thẳng với bài này: tư duy hệ thống (systems thinking, mục 5.2.4).

Theo chuẩn, tư duy hệ thống chống lại một lối làm việc thiển cận: dùng những chuyên gia (người lo mạng, người quản trị database, người quản trị hệ thống) hiếm khi trao đổi với đội phát triển hoặc đội vận hành, và không hiểu hệ thống như một tổng thể. Ngược lại, chuẩn viết rằng trong DevOps, cách nhìn bao quát khuyến khích người làm kỹ thuật hiểu trọn hệ thống từ đầu tới cuối.

Với một request thì "từ đầu tới cuối" chính là chín chặng của Hình 4, nên tấm bản đồ trong bài này là bước đầu của việc hiểu trọn đó.

Vì sao bước đầu lại là một tấm bản đồ? Vì muốn chịu trách nhiệm cho một chặng thì trước hết phải biết nó nằm ở đâu và hỏng ra sao.

Chốt

Trong chín chặng, chỉ ứng dụng là code bạn viết. Code không đổi mà web vẫn hỏng thì nghi tám chặng hạ tầng trước. DevOps, theo chuẩn IEEE 2675-2021, là các nguyên tắc và cách làm để người viết phần mềm và người giữ nó chạy cộng tác suốt vòng đời, bắt đầu từ việc cùng hiểu trọn đường đi này.

Tấm bản đồ vẽ trên giấy thì dễ gật đầu, nhưng làm sao biết các chặng đó có thật? Ba lab dưới đây cho bạn thấy chúng ngay trên máy mình, bằng output thật: hỏi DNS, mở kết nối TCP, bắt tay TLS, gửi request HTTP, và dấu vết của một reverse proxy ở phía server. Bước tự gây lỗi nằm ở Lab 1, và tất cả chạy trong Git Bash trên Windows.

# Lab 1: DNS
nslookup github.com
nslookup khong-ton-tai-dau-nhe-12345.com

# Lab 2: DNS, TCP, TLS, HTTP và reverse proxy nối tiếp nhau
curl -v https://example.com

# Lab 3: TLS, đọc chứng chỉ của một website thật
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates

Lab 1: DNS, và một tên miền không tồn tại

Lệnh đầu hỏi địa chỉ của github.com, và output của nó là Hình 5.

Lệnh thứ hai là bước tự gây lỗi: cố tình hỏi một tên miền chắc chắn chưa ai đăng ký. Kết quả Non-existent domain ở Hình 6 là kiểu hỏng đơn giản nhất của bước hỏi DNS.

Trước khi đọc tiếp, thử tự trả lời: lúc nhận câu trả lời đó thì request đã đi tới đâu trên bản đồ?

Nó chưa qua khỏi bước hỏi DNS. Chưa có kết nối nào được mở, nên những suy đoán kiểu "server chết" hay "firewall chặn" loại được ngay.

Lab 2: một request trọn vẹn

Lab 1 mới soi một chặng. Muốn thấy cả chuỗi chặng nối nhau thì dùng curl với cờ -v (verbose), là cờ bảo nó kể lại từng bước nó làm. Output sẽ khá dài, vậy đọc thế nào cho khỏi rối? Nhìn ký tự đầu mỗi dòng, vì ký tự đó cho biết dòng thuộc loại nào:

  • * là lời curl tự kể: đang hỏi DNS, đang kết nối, đang bắt tay TLS.
  • > là dữ liệu gửi đi.
  • < là dữ liệu nhận về.

Dưới đây là những dòng chính trong output thật trên máy mình ngày 24/09/2026, xếp theo thứ tự các chặng trên bản đồ:

* IPv4: 104.20.23.154, 172.66.147.243      ← DNS đã trả lời
* Established connection to example.com
  (104.20.23.154 port 443)                 ← TCP: kết nối đã mở
* ALPN: server accepted http/1.1           ← bắt tay TLS xong
> GET / HTTP/1.1                           ← request HTTP đi ra
> Host: example.com
< HTTP/1.1 200 OK                          ← response đi về
< Server: cloudflare                       ← reverse proxy đã trả lời
Hình 9 Output của curl -v https://example.com, rút gọn và ghi chú từng chặng. Dòng đầu liệt kê các địa chỉ IPv4, tức loại địa chỉ bốn số đã gặp ở tab URL. Bản đầy đủ và cách đọc từng dòng nằm ở trang Đọc output thật.

Dòng cuối, Server: cloudflare, cho biết ai đã trả lời request. Sao lại là Cloudflare mà không phải máy của chủ website? Vì đứng giữa người dùng và máy đó có một reverse proxy của công ty Cloudflare, và chính nó đã trả lời request tới example.com. Trên bản đồ, reverse proxy này đứng đúng chỗ của Nginx, chỉ khác là do một nhà cung cấp lớn vận hành.

Lab 3: đọc chứng chỉ

Bản curl đi kèm Git for Windows không in thông tin chứng chỉ (lý do ở trang Đọc output thật). Vậy muốn xem chứng chỉ thì dùng gì? Dùng openssl, bộ công cụ mật mã có sẵn trong Git Bash. Output của nó là Hình 7.

Câu lệnh trông dài vì nó nối hai lệnh openssl với nhau, và trong đó có hai ký hiệu lạ. Dấu | đưa output của lệnh trước làm input của lệnh sau, còn phần 2>/dev/null giấu các dòng chẩn đoán. Biết chừng đó là đủ để chạy lab này.

Câu hỏi của lab là: chứng chỉ do ai ký, và còn bao nhiêu ngày thì hết hạn? Với output ở Hình 7, chạy ngày 24/09/2026, thì còn 33 ngày. Chạy hôm nay bạn sẽ thấy một chứng chỉ khác, vì chứng chỉ được thay định kỳ trước khi hết hạn, nhưng cách đọc thì vẫn vậy.

Tự vẽ lại bản đồ

Bước cuối không cần lệnh nào. Bạn đóng tài liệu lại, lấy giấy vẽ chín chặng theo thứ tự, rồi cạnh mỗi chặng ghi nó chạy ở đâu và output nào trong ba lab vừa rồi đã cho bạn thấy nó. Sẽ có chặng không gắn được với dòng output nào, vì ba lab này không chạm tới firewall, ứng dụng và database.

Tự kiểm tra

Bấm vào từng dòng khi bạn tự trả lời được mà không nhìn lại tài liệu. Cách tự chấm: giải thích được cho một người chưa học bài này, bằng lời của bạn, kèm một ví dụ.

  • Giải thích được từng phần của https://example.com:443/users/42?x=1, và phần nào là việc của code
  • Kể được chín chặng theo đúng thứ tự, và chặng nào chạy ở đâu
  • Chỉ ra được một dòng output của curl -v thuộc chặng nào
  • Nói được khi gặp Non-existent domain thì request đã dừng ở chặng nào
  • Đọc được ai ký chứng chỉ của một website và khi nào nó hết hạn
  • Nói được vì sao trong chín chặng chỉ ứng dụng là code của bạn, và điều đó đổi cách bạn tìm lỗi ra sao
  • Nói lại được định nghĩa DevOps của chuẩn IEEE 2675-2021 bằng lời của bạn, và chỉ ra các bên liên quan trong định nghĩa ấy là ai trên tấm bản đồ chín chặng

Những chỗ hay hiểu sai

Bảng 3 Những cách hiểu nghe hợp lý nhưng sai về các chặng trong bài này.
Dễ nghĩ làThực tếXem lại
Web không vào được thì mở code ra xem trước.Code không đổi thì lỗi thường nằm ở tám chặng hạ tầng.Phía server
Chứng chỉ là thứ gắn tên miền với địa chỉ IP.Gắn tên với địa chỉ là việc của DNS. Chứng chỉ, được kiểm tra lúc bắt tay TLS, chứng minh rằng máy đang trả lời có quyền dùng tên miền đó.TLS
nslookup tự biết địa chỉ của mọi tên miền.Nó chỉ hỏi một server DNS rồi in lại câu trả lời, còn dòng Server: cho biết ai đã trả lời.DNS
DevOps là một công cụ, hoặc tên một chức danh.Chuẩn IEEE 2675-2021 định nghĩa DevOps là một tập hợp nguyên tắc và cách làm giúp các bên liên quan cộng tác tốt hơn suốt vòng đời phần mềm. Công cụ chỉ giúp áp dụng các nguyên tắc ấy.DevOps

Kết lại

Câu hỏi của bài là: từ lúc gõ một địa chỉ vào trình duyệt tới lúc code Spring Boot của bạn chạy, request đi qua những chặng nào, và chặng nào thật sự là code của bạn?

Nó đi qua chín chặng: trình duyệt, DNS, mở kết nối, mã hoá, gửi request, firewall, reverse proxy, ứng dụng, database. Năm chặng đầu chạy trên máy người dùng, bốn chặng sau chạy trên server, còn ở giữa là Internet.

Còn chặng nào là code của bạn? Trong cả đường đi, chỉ ứng dụng là code bạn viết. Tám chặng còn lại là hạ tầng, nên khi code không đổi mà web hỏng thì đó là chỗ cần nghi trước.

Tám chặng ấy cũng là chỗ chữ DevOps có nghĩa. Chuẩn IEEE 2675-2021 định nghĩa DevOps là tập hợp các nguyên tắc và cách làm giúp các bên liên quan cộng tác tốt hơn để đặc tả, phát triển và vận hành phần mềm, đồng thời cải tiến liên tục suốt vòng đời của nó. Một trong các nguyên tắc ấy là tư duy hệ thống, khuyến khích người làm kỹ thuật hiểu trọn hệ thống từ đầu tới cuối. Tấm bản đồ chín chặng là bước đầu của việc đó.

Câu hỏi còn mở

Trong chín chặng, chặng nào dễ vấp trước nhất? Bước mở kết nối tới đúng máy và đúng chương trình, vì nó hỏng được ngay trên máy bạn, chưa cần tới server. Từ đó còn lại một câu hỏi:

App chạy bình thường khi gọi bằng localhost:8080 trên chính máy mình, nhưng người khác gọi vào thì không được. Chặng mở kết nối hỏng ở đâu?

Nguồn đọc thêm

Danh sách chỉ gồm nguồn chính thức: văn bản chuẩn gốc và tài liệu của chính dự án. Không cần đọc hết ngay, cứ để đó tra khi cần.

  1. RFC 3986, Uniform Resource Identifier (URI): Generic Syntax. Cấu trúc của URL. rfc-editor.org/rfc/rfc3986
  2. RFC 1034 và RFC 1035, Domain Names. Nền tảng của DNS. rfc-editor.org/rfc/rfc1035
  3. RFC 9293, Transmission Control Protocol (TCP). rfc-editor.org/rfc/rfc9293
  4. RFC 8446, The Transport Layer Security (TLS) Protocol Version 1.3. rfc-editor.org/rfc/rfc8446
  5. RFC 9110, HTTP Semantics. Phương thức, header, port mặc định của http và https. rfc-editor.org/rfc/rfc9110
  6. Nginx, Beginner's Guide, tài liệu chính thức. nginx.org/en/docs/beginners_guide.html
  7. curl, trang hướng dẫn chính thức, gồm cờ -v. curl.se/docs/manpage.html
  8. IEEE 2675-2021, IEEE Standard for DevOps: Building Reliable and Secure Systems Including Application Build, Package, and Deployment. Chuẩn định nghĩa DevOps; trang giới thiệu chính thức, toàn văn phải trả phí. standards.ieee.org/ieee/2675/6830
  9. ISO/IEC/IEEE 32675:2022, bản chuẩn quốc tế được chấp nhận từ IEEE 2675-2021; trang giới thiệu chính thức, toàn văn phải trả phí. iso.org/standard/83670.html

Nguồn ảnh

Ảnh chụp lấy từ Wikimedia Commons, dùng theo giấy phép ghi ở từng dòng; ảnh đã được chuyển sang đen trắng và cắt khung cho hợp bố cục trang; ảnh chụp đồ vật còn được tách khỏi nền cũ để đặt thẳng lên nền trang. Ảnh chụp màn hình do mình tự chụp. Truyện tranh của xkcd và của turnoff.us dùng theo giấy phép phi thương mại ghi ở từng dòng; đã đổi cỡ và chuyển sang đen trắng.

  1. Ảnh 1Ví dụ thành phần URI, RFC 3986, chụp màn hình rfc-editor.org, 30/09/2026.
  2. Ảnh 2Router gia đình Linksys WRT54G, Evan-Amos, Wikimedia Commons, public domain.
  3. Ảnh 3We need to talk about SSL, Daniel Stori, turnoff.us, CC BY-NC-SA 4.0.
  4. Ảnh 4Mẫu cáp quang biển, Lonnie Hagadorn, Wikimedia Commons, CC BY-SA 4.0.
  5. Ảnh 5Trang chào mặc định của Nginx, chụp màn hình Nginx 1.30.5 chạy trên máy mình, 30/09/2026.
  6. Ảnh 6Dependency, Randall Munroe, xkcd.com, CC BY-NC 2.5.