| 联系博客音乐支持

Website Deployed but Nothing Changed

Introduction

If you have built a website, you have likely encountered the specific frustration of deploying to the global network: you update a CSS style or patch a JavaScript bug, but the production site stubbornly serves the old version. Only your users see the "broken" state. This is especially painful during emergency hotfixes or when debugging environment-specific issues on platforms like PHP.

I personally lost hours debugging this exact blog, simple PHP and CSS because of stale code. What takes 1 hour on localhost can take 5 in production. On localhost, the loop is tight. On the open web, you are fighting against multiple layers of optimization designed to prevent exactly what you are trying to do. Which is download fresh files.

The Stack Trace of Caching

If your site isn't updating, work through the failure points in this order, from "User Error" to "Architecture".

1. Failed to Deploy

Before blaming the network, blame the pipeline. Despite the title "deployed but nothing changed", there is a non-zero chance you haven't actually deployed.

Did the GitHub Action runner actually turn green? Did the rsync script fail silently due to permissions or clock skew? Always verify the file modification time on the server first. ls -l can help you check the file time. If the file is old on the disk, no amount of cache purging will fix the client.

2. Caching Layers

The most common culprit is caching. It's not a single entity; it is a stack. You have Server caching (PHP OpCode), Load Balancers, DNS, CDNs, ISP transparent proxies, and finally, the user's browser. Even one stale layer breaks the chain.

Layer 1: The Browser Cache

Browsers aggressively cache static assets (CSS, JS, Images) to save battery and data. For Local Debugging:

For Production (The Configuration Fix): To ensure all clients see changes immediately, you must instruct the browser not to trust its local copy. You do this with the Cache-Control HTTP header. The directive no-cache, no-store, must-revalidate is the nuclear option for ensuring freshness.

Nginx Configuration: Add this to your server or location block for your static assets:

location ~* \.(css|js|png)$ {
    add_header Cache-Control "no-cache, no-store, must-revalidate";
}

Apache Configuration (.htaccess): Ensure mod_headers is enabled and add this:

<FilesMatch "\.(css|js|png)$">
    <IfModule mod_headers.c>
        Header set Cache-Control "no-cache, no-store, must-revalidate"
    </IfModule>
</FilesMatch>

Note: This forces every client to hit your server for every asset, maximizing freshness but increasing server load.


Layer 2: Network & Edge Caches (CDNs)

If you use a CDN (like Cloudflare, AWS CloudFront, or Fastly), setting headers on your origin server isn't enough if the CDN has already cached the old file. The Cache-Control header will eventually propagate, but for immediate fixes, you must Purge the cache.


Layer 3: Server-Side Caching (OpCode)

For interpreted languages like PHP, the server often caches the compiled bytecode (OpCode) in memory to avoid parsing the script on every request.

The Ultimate Fix: Versioning (Cache Busting)

Purging caches and wrestling with headers is reactive. The industry-standard solution is Versioning (or Cache Busting).

Browsers identify files by URL.

You don't need to manually rename files. Since you are using PHP, automate it by appending the file's Modification Time as a query parameter.

The PHP Auto-Versioning Strategy: Instead of a static link:

<link rel="stylesheet" href="style.css">

Inject the file's timestamp dynamically:

<link rel="stylesheet" href="style.css?v=<?php echo filemtime('style.css'); ?>">

How this solves everything:

  1. filemtime reads the Unix timestamp of the last save (e.g., 1678123456).
  2. The rendered HTML is <link href="style.css?v=1678123456">.
  3. As long as you don't touch the file, the URL stays the same (Cache Hit).
  4. The second you deploy a change, the timestamp updates, the URL changes, and every browser, CDN, and proxy is forced to fetch the new version instantly.

Conclusion

Debugging deployment issues is about eliminating variables.

  1. Verify the file is actually updated on the server disk.
  2. Bypass your local cache (DevTools).
  3. Control downstream caching with Cache-Control headers (Nginx/Apache).
  4. Automate freshness with Cache Busting version strings.

Implement these patterns, and you will stop wondering why your deployed code looks like last week's backup. If you want ready-to-deploy Nginx and Apache configurations that handle this tuning out-of-the-box, consider joining my premium plan.

Support the Research

I spend about 6 hours a week outside of my main work to write these deep-dives into C, Java, and systems performance. If my work adds value to your engineering toolkit, there are two ways to support the project:

Premium Lab Access

1-week early access + full source code and configs for all blogs. Join for $9/mo

One-Time Support

Simple way to say thanks for a specific fix or tutorial. Buy a Coffee ($5)

Other ways to support

You can visting the Support for more ways to support me and sustain the blog community.

博客