Skip to content

Deploying a website and CDN

The same page, sent from two machines in the same building in Hong Kong. This is what a visitor in China gets:

one page, two lines, both starting nowthe gatewayoptimisedyour machineordinaryyour machinethe upper line streams 4K without a pause; the lower one cannot open a page at all at 9pma lost piece has to be sent again — which is why the lower one is not merely slower

The quick one is a three-network optimised line; the other is an ordinary one. Same hardware, same cable out of the rack, same distance to the visitor — the only difference is that one door in the middle.

Here is what that door is worth to a customer of yours in China:

The same siteoptimisedordinary
Daytimeopens at onceusable, a bit slow
8pm to midnightstill opens at once. 4K video seeks anywhere and plays throughimages stall half-loaded, pages spin until they time out — the site simply does not open

And 8pm to midnight is when people shop.

Which is the problem with selling to both sides of the world: no single machine is good at both. The ones that are fast into China are small and expensive, and the ones that are big and cheap are the lower line.

So rent two, and put the small fast one in front. That is the whole idea, and the rest of this page is why it works and how to build it.

§1–§5why one line costs ten times what another does§6–§8the shape of the answer, and what it actually saves§9–§11what to type, what goes wrong, when not to bother

If you only ever sell to one side of the world, skip to §11 — you probably do not need any of this.


1. Two roads, the same distance

Put numbers on what you just watched. A visitor in Shanghai asks both machines for the same page. One answers in 40 milliseconds and loses nothing; the other takes 300 and drops a tenth of what it was sent.

Nothing about the distance changed. What changed is which road the traffic was sold the right to use.

the same parcel, the same distanceordinaryyour machinethe public depotqueued from 8pm to midnightyour visitorslower when it matters, and some of it never arrivesoptimisedyour machinea lane somebody paid to reserveyour visitorthe same at 9pm as at 9am

A three-network optimised line — 三网优化 — is the second one, for all three of China's carriers at once. §2 says what the three are and why "all three" needs saying.


2. The names you will see in a listing

China has three carriers, and your visitor is on one of them. Which one is not your choice and not theirs — it is whoever wired their building.

carrierroughly how many peopleChina Telecom · 电信the largest share of home broadbandChina Unicom · 联通strong in the northChina Mobile · 移动most phones, and a lot of home broadband now

Each carrier sells an ordinary road and a premium one, and they are separate purchases. A machine can be excellent for Telecom and terrible for Mobile. That is the single most common way somebody pays for a "good line" and still has half their customers complaining.

What the listing saysWhoseIn plain words
163, AS4134Telecom, ordinaryThe public motorway. Fine at 3pm, a car park at 9pm.
CN2 GT, AS4809Telecom, middleA better motorway. Still queues at the same hours.
CN2 GIA, AS4809Telecom, premiumThe reserved lane. This is the one people mean.
169, AS4837Unicom, ordinaryThe public motorway again.
AS9929, CUIIUnicom, premiumThe reserved lane.
CMI, AS58453Mobile, ordinaryPublic, and often the most roundabout of the three.
CMIN2Mobile, premiumThe reserved lane.
三网优化, three-network optimisedall threeOne reserved lane bought from each.

Two words worth knowing, because sellers use them and they are not the same thing:

  • 去程 — visitor to your machine. Small. Requests and clicks.
  • 回程 — your machine back to the visitor. Everything else: pages, images, video. This is the leg that decides whether your site feels fast, and it is the leg a seller means when they advertise CN2 GIA.

A listing that names a premium network for only one carrier is telling you the truth about that carrier and saying nothing about the other two. Ask.


3. How big the difference is

Typical, not a promise — measure your own before you commit. Hong Kong to mainland China:

ordinary linethree-network optimised
Daytime60–90 ms, little loss30–50 ms, no loss
8pm to midnight150–300 ms, 5–30% loss30–60 ms, no loss
How it feelsimages half-load, checkout hangs, some people simply cannot paythe same all day
one day, one page, two machines in the same rackhigher is slower8pm – midnightordinaryoptimisedmidnight6amnoon6pmmidnight

The dropped packets hurt more than the delay does. A slow page is annoying; a page missing one file in ten is broken. And the loss is not spread evenly — it arrives in the four hours when people are at home with a phone in their hand, which for a shop is most of the day's money.

Testing it honestly

Two rules, and they are both about when:

  • Test between 9pm and 11pm, China time. A daytime test tells you nothing. Every line on the market looks good at 3pm.
  • Test from the visitor's side, on a real Chinese connection, on each of the three carriers if you can borrow the phones. Downloading a big file from your machine and watching the speed is the whole test.

From inside the container, app-setup install nettools gets you mtr, traceroute and dig. Be aware that a traceroute run from a container often shows gaps in the middle — the replies from intermediate hops do not always make it back through the container's networking — so treat it as a hint, not the answer. The number that matters is the one measured in China.


4. Why the fast one is fast

The ocean is not the problem. Both lines cross it on the same cables, in about the same few milliseconds, and neither is ever short of capacity out there.

where the queue actually isyour machineundersea cablenever the problemthe gatewayinto Chinayour visitoreverything queues herean ordinary line waits its turna premium line has a share held for ittwo thousand kilometres of it is fine. It is the last two hundred metres that is sold by the megabit.

The queue is at the last door. Everything entering mainland China from outside crosses a gateway. Its capacity is fixed and the three carriers own it. An ordinary line queues for whatever is spare; a premium line has a share held for it however long the queue is.

And the road may not be straight

A cheap Hong Kong line can send your data to the United States and bring it back to Shanghai. Not a small detour — about twenty times the distance.

the same two points, two ways roundmainland ChinaUnited StatesShanghai · your visitorHong Kong · your machineLos Angelesdirect · 1,200 kmabout 40 msand the answer comes back the same wayan ordinary line may go via the US — some 22,000 km, about 300 msNobody sends you a bill for the detour. You pay it in waiting, on every click.

It happens because the seller bought the cheapest transit available, and what that transit does next is not theirs to decide.

So a premium line is three things at once: a share of the gateway, a route that does not wander, and the return leg. That last one is worth naming separately. Getting traffic out of your machine is easy and nobody charges much for it. Getting it back in to a Chinese visitor along a good path is a product a carrier prices on its own — and it is the expensive half.


5. Why it costs what it costs

Rough shape, in 2026, and prices move — check the day you buy:

For the same ¥100 a monthyou get
an ordinary linea 1 Gbps port, unmetered, shared with a few dozen neighbours
three-network optimised20–50 Mbps, or a few hundred GB of quota

Ten to fifty times the price per megabit. Everything else on this page is a consequence of that one number.

Where does the difference go? Follow the same ¥100 into each:

the same ¥100 a month, and where it actually goesordinary,high specthe machine · 8 cores, 16 GBlinemachine ¥85 · line ¥15 — the bandwidth is oversold, so it is nearly freethree-networkoptimisedmachinethe line · 30 Mbps, or 500 GB of quotamachine ¥15 · line ¥85 — the same small box in an ordinary rack costs ¥15The box is cheap. The road is not — so buy only as much road as you will use.

The machine itself is nearly worthless. A 1-core, 1 GB box in an ordinary rack is about ¥15 a month. Move that identical box into a three-network optimised rack and it is ¥100. The extra ¥85 buys no cores, no memory and no disk. It is all road.

And the road is priced that way for four reasons, which compound:

what makes it expensivethe everyday versionit is not oversolda buffet seat versus a table booked in your namethe gateway is finitethree companies own the only bridges, and nobody is building morethe return leg is a separate productthe trip out is cheap; the trip back is what you actually pay forthree carriers, three purchasesone reserved lane is not three reserved lanes

The first one is most of it. An ordinary "1 Gbps unmetered" port is sold to thirty people on the assumption they will not all want it at once — which is true at 3pm and false at 9pm, and that is precisely why the ordinary line collapses at the hour it does. A premium megabit is sold once. When it is gone it is gone, and the price is what a scarce thing costs.

The fourth is why 三网优化 costs more than a line advertised as "CN2 GIA". It is three separate premium arrangements on one machine, and you are paying for all three.

Which means the honest summary is: you are not buying a fast server, you are renting road. So rent as little of it as you can — which is §6.


6. So do not buy one machine

Look at what your website actually consumes, next to what the expensive line gives you:

three-network optimisedordinary, high spec
What it is good atgetting bytes into China at 9pmcores, memory, disk, and traffic that costs almost nothing
What it is bad ateverything is small and everything is meteredthe four hours a day your Chinese customers are shopping
What ¥100 a month buys1 core, 1 GB, 500 GB of traffic8 cores, 16 GB, 500 GB of disk, terabytes of traffic

Your database, your PHP, your image resizing, your build — none of that cares what the network is like. It wants cores and memory, and on a premium-line machine cores and memory carry the road's price whether you use the road or not (§5's bars).

So put the site on the cheap machine and buy only the road on the expensive one.


7. The plan

Two containers, in the same city, from the same host or from two:

visitors in Chinathe front nodeoptimised line · 1 core, 1 GBnginx, and nothing else3 msthe originordinary line · 8 cores, 16 GByour actual websiteeveryone elsestraight there — no detour through China's front door
  • Visitors in China land on the front node. Anything it has already seen it answers itself, over the expensive line, in 40 milliseconds. Anything it has not, it fetches from the origin — which is 3 milliseconds away, because you deliberately rented both in the same city.
  • Everyone else goes straight to the origin and never touches the front node at all. A customer in Berlin should not be routed through Hong Kong's optimised line to reach a server that is right there.

That last split is what makes this work for a cross-border shop rather than just for a Chinese one. It needs one thing from your DNS provider: the ability to answer differently depending on where the question came from. DNSPod, Alibaba Cloud DNS, Huawei Cloud DNS and most Chinese providers call this 分线路解析 and give you a 境内 / 境外 split for free.

NameLinePoints at
www.example.comChina (境内)the front node's machine
www.example.comDefault (境外/默认)the origin machine
origin.example.comDefaultthe origin machine

No split-line DNS? Point www at the front node for everybody. Foreign visitors pay one extra hop of a few milliseconds — usually nothing, occasionally enough to matter. Get it working this way first and add the split later.

origin.example.com is the third row and it is not optional: it is how the front node reaches the origin, and how you check the origin when the front node is what broke.


8. What the cache saves, and what it does not

A cache is a shelf by the door. The first person to ask for a file makes somebody walk to the back to fetch it; everyone after that gets it off the shelf.

who askswhat happensthe first visitor wants logo.pngthe front node has never seen it — fetches it from the origin, keeps a copy, hands it overthe next ten thousandstraight off the shelf. The origin is never told.somebody adds to their cartnever cached, never shelved — always the origin

Now the honest accounting, because one row of this surprises people:

does caching save it?
What the front node sends to visitorsNo. Every byte a visitor asks for leaves that node, cached or not. This is the number that has to fit in its quota.
What the front node pulls from the originYes, nearly all of it. And since inbound traffic counts against your quota too, this is a real saving, not a bookkeeping one.
The origin's CPU, memory and diskYes, a lot. A cached page is a PHP request that never ran.
How long the visitor waitsYes. A hit skips the round trip to the origin entirely.

Put numbers on it. Say your site sends 300 GB a month to visitors:

front node's metered traffic
No cache at all300 GB out + 300 GB pulled from the origin = 600 GB
80% of it cacheable and cached300 GB out + 60 GB pulled = 360 GB

Both directions count on an hqnode container (using your container §3), so the cache roughly halves what you spend on the expensive machine. That is on top of the speed.

Size the front node by traffic, not by cores. One core and a gigabyte of memory serves an enormous amount of static content. The quota is what runs out.


9. Building it

The example: www.example.com is the shop, the front node's machine is 203.0.113.10, the origin's is 203.0.113.20.

Step 1 — get the two containers

Ask your host for one on a machine with an optimised line and one on a big ordinary machine, in the same city. "Same region" is not close enough — Hong Kong to Tokyo is 50 ms, and you would be adding it to every uncached request.

Tell your host what you are doing; picking two machines that are physically near each other is easy for them and impossible for you to check from outside.

Step 2 — put the website on the origin

Nothing special about this half. It is an ordinary deployment on an ordinary machine: Deploying an LNMP site, Deploying a Node.js app, or whatever you already have.

Step 3 — give the origin a name

In the origin container:

sh
app-setup domain add origin.example.com 80

That gives it a certificate the machine manages for you, which is what you want here — there is nothing secret about it and one less thing to renew. Add the DNS record for origin.example.com first, or the certificate cannot be issued: an A record at 203.0.113.20.

Check it before going any further:

sh
curl -I https://origin.example.com/

If that is not a 200, stop here. Nothing downstream can be right.

Step 4 — nginx on the front node

The short way, if you would rather not type eighty lines. app-setup has this whole step as one entry — cdn, on the Web servers tab. Install it, and fill in three fields:

sh
app-setup install cdn
FieldHere
Originorigin.example.com
Origin port443
Domain on this nodewww.example.com,example.com

It writes the same file this section writes, from those answers. It caches files only — images, video, styles, scripts — and passes every page, PHP file and API call straight through, which is the setting nothing private can leak through; turning on page caching is a second, deliberate decision and app-setup docs cdn says what to check first. The file it writes ends in eight worked examples, commented out: never caching an API, slicing video for seeking, WordPress and WooCommerce, hotlink protection.

The rest of this section is that same config written by hand. It is worth reading once whichever way you got it — everything that goes wrong in §10 is one of these lines.

In the front node's container:

sh
app-setup install nginx

Then write one file — /etc/nginx/http.d/cdn.conf on Alpine, /etc/nginx/conf.d/cdn.conf on Debian or Ubuntu:

nginx
# Where cached files live, and how much to keep. /data survives a
# reinstall; the cache need not, but the disk quota is shared anyway.
proxy_cache_path /data/cache levels=1:2 keys_zone=site:20m
                 max_size=4g inactive=7d use_temp_path=off;

# A request carrying any of these cookies belongs to one person and must
# never be served to another. Add your own cart and session cookies here.
map $http_cookie $private {
    default                        0;
    "~*wordpress_logged_in"        1;
    "~*woocommerce_items_in_cart"  1;
    "~*wp_woocommerce_session"     1;
    "~*comment_author"             1;
}

upstream origin {
    server 203.0.113.20:443;   # the origin machine, by address
    keepalive 32;              # reuse connections, no re-handshake
}

server {
    # The machine already terminated HTTPS on 443.
    listen 80;
    server_name www.example.com example.com;

    # Everything below this line is inherited by all three locations.
    proxy_http_version 1.1;
    proxy_set_header Connection "";

    # Ask the origin machine for this name so its front door knows whose
    # site this is — even though the request carries the visitor's name.
    proxy_ssl_server_name on;
    proxy_ssl_name origin.example.com;

    proxy_set_header Host              $host;
    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;

    proxy_cache site;
    # Ten people, one file, one fetch.
    proxy_cache_lock on;
    # Origin having a bad day: serve the shelf copy, not an error.
    proxy_cache_use_stale error timeout updating
                          http_500 http_502 http_503 http_504;
    add_header X-Cache $upstream_cache_status always;

    # Images, styles and scripts: keep them a month. Video is left out
    # on purpose — it is the priciest thing to send from the costly box.
    location ~* \.(jpe?g|png|gif|webp|avif|svg|ico|css|js|woff2?)$ {
        proxy_pass https://origin;
        proxy_cache_valid 200 30d;
        proxy_ignore_headers Set-Cookie Cache-Control Expires;
    }

    # Admin, login, cart, checkout: not one byte on the shelf.
    location ~* ^/(wp-admin|wp-login|cart|checkout|my-account) {
        proxy_pass https://origin;
        proxy_cache off;
    }

    # Everything else: ten minutes; signed-in visitors skip the shelf.
    location / {
        proxy_pass https://origin;
        proxy_cache_valid 200 301 302 10m;
        proxy_cache_valid 404 1m;
        proxy_cache_bypass $private;
        proxy_no_cache     $private;
    }
}
sh
mkdir -p /data/cache
nginx -t          # says what is wrong before anything goes live
rc-service nginx restart      # Alpine
systemctl restart nginx       # Debian, Ubuntu, the rest

The package's own default site can stay where it is. It has no server_name, so it only answers requests that match nothing else.

Step 5 — give the front node the real name

In the front node's container:

sh
app-setup domain add www.example.com 80
app-setup domain add example.com 80

The machine terminates HTTPS on 443, renews the certificate itself, and hands nginx plain HTTP on 80 — which is why the config above listens on 80 and there is no certificate anywhere in it. It also fills in X-Forwarded-For before nginx sees the request, so the address your origin logs is the visitor's, not Hong Kong's.

Prefer to hold your own certificate? app-setup domain add www.example.com 8443 self-hosted and terminate in nginx instead. Running a proxy or VPN §7 has the trade-off in full.

Step 6 — point DNS

The three records from §7. Give it a few minutes.

Step 7 — check that the shelf is being used

sh
curl -sI https://www.example.com/logo.png | grep -i x-cache
curl -sI https://www.example.com/logo.png | grep -i x-cache
# first says  X-Cache: MISS
# second says X-Cache: HIT

MISS then HIT is the whole thing working. If the second one is still MISS, §10 has the four usual reasons.

To throw the cache away after you change something:

sh
rm -rf /data/cache/*
nginx -s reload

10. What goes wrong

What you seeWhat it usually is
Always MISS, never HITThe origin is sending Set-Cookie or Cache-Control: no-cache on everything. WordPress does this when a plugin starts a session on every page. Either fix it at the origin or add proxy_ignore_headers Set-Cookie; to the location / block — and read the next row before you do.
One customer sees another's cartYou cached something private. This is the failure that costs you a customer rather than a second. Every path in §9's second location block, plus the cookie map, exists to stop it. Test it: log in, add something to the cart, then open the same page in a private window.
Changed an image, visitors see the old oneIt is on the shelf for thirty days, as instructed. Clear the cache, or better, change the filename — logo.v2.png — and never think about it again.
Front node's traffic quota gone in a weekExpected, if you sized it by cores. Every visitor byte goes through it. §8 has the arithmetic.
Everything redirects in a loopThe origin is redirecting to its own name. Keep proxy_set_header Host $host; exactly as written — the origin must see the visitor's name, not origin.example.com.
Origin logs show one IP for the entire internetThe origin is not reading X-Forwarded-For. That is a setting in whatever runs there, not here.
502 from the front nodeIt cannot reach the origin. Check curl -I https://origin.example.com/ from inside the front node's container.
Google indexed origin.example.comIt is a real, reachable copy of your shop, so of course it did. Have the origin return X-Robots-Tag: noindex when the requested name is origin.example.com.

One trap worth stating on its own: do not put both containers on the same machine. Beyond it defeating the entire purpose, it does not even work as written — a container that connects to its own machine's public address reaches itself, not its neighbour, so the front node would proxy to itself and loop. Two machines, two addresses.


11. When you should not do this

This is real work and it adds a thing that can break. Skip it when:

if this is youdo this insteadall your customers are outside Chinaone ordinary machine. You were never paying the premium.all your customers are inside Chinaa machine inside China, with the domain filed (备案). Cheaper and faster than anything here.a small site, a few GB a monthone optimised-line container, running the site directly. Two machines is not worth the wiring.you need it fast in ten countriesa commercial CDN. This design has one front node; they have two hundred.

Both machines in this design sit outside mainland China, which is why nothing here mentions filing. Move the origin inside and the domain needs an ICP filing before it may answer on 80 or 443 at all — that is a change of plan, not a change of configuration.

And if you are unsure whether the premium line is worth it: buy the small one for a month, put a copy of one page on it, and compare at 9pm. It is the cheapest experiment on this page.


Where next