Why Move a Legacy App to Workers?
Cloudflare Workers and similar serverless platforms keep pushing down costs while simplifying global application deployment. That combination makes legacy migrations increasingly attractive. The motivations typically fall into two categories: the constraints pushing teams off old infrastructure, and the benefits pulling them toward a serverless, zero-trust model.
Legacy setups usually suffer from inflexible compute that demands constant maintenance, leading to either capacity shortages or wasted spend. Managing VPN credentials adds friction and introduces security risk when done carelessly, and VPN client software often trips up non-technical users. On the pull side, serverless platforms bring instant scaling with no capacity ceilings and no idle cost, while zero-trust access eliminates separate credential systems by letting users authenticate through SSO. The accumulated infrastructure — VPN hardware, private cloud, old compute — can be retired entirely, simplifying operations and cutting costs.
What stops many organizations is the perceived migration effort. In practice, the platform features below collapse that effort into a few focused steps. This walkthrough uses a contrived Node.js application, Post Process, to illustrate a realistic migration path with four Cloudflare technologies: Workers for compute, Wrangler for tooling, Access for zero-trust security, and Argo Tunnel for secure origin connectivity.
Starting Point: Application and Architecture
The example application accepts an HTTP POST, parses the JSON body, performs text processing, and returns the result. It currently runs on Node.js on a laptop, behind a gateway that controls access.
The code is split cleanly into two modules. One module handles all Node.js-specific HTTP and preprocessing work. The other, postProcess.js, exposes a single function that takes a parsed JSON object and synchronously returns a plain JavaScript object ready for JSON serialization. That business-logic module is portable to Workers with no changes; only the HTTP layer needs to be rebuilt.
Because the application is JavaScript, the business logic can be copied directly into the new Workers project. The same approach extends to other languages via WebAssembly; for those, a rewrite or transpilation step is required first.
Target Design
Three decisions shape the new Cloudflare-based architecture:
- Keep the business logic byte-for-byte identical, using the same
.jsasset. - Build a new preprocessing and HTTP layer in Workers to replace the Node.js module.
- Put Cloudflare Access in front of the application for authentication.
curl -X POST https://postprocess-workers.kirk.workers.dev/postprocess --data '{"operation":"2","data":"Data-Gram!"}'
First Win: Zero-Trust Before Compute
A successful migration benefits from an early win — something useful that also eases the eventual cutover. Solving the security problem ahead of the compute problem is exactly that.
Using cloudflared, the terminal flow is minimal. Run cloudflared tunnel login to authenticate, then create a tunnel from the local server to the desired public hostname:
cloudflared tunnel --hostname postprocess.kschwenkler.com --url localhost:8080
const server = http.createServer((req, res) => {
if (req.url == '/postprocess') {
if(req.method == 'POST') {
gatherPost(req, data => {
try{
jsonData = JSON.parse(data)
} catch (e) {
res.end('Invalid JSON payload! \n')
return
}
result = postProcess(jsonData)
res.write(JSON.stringify(result) + '\n');
res.end();
})
} else {
res.end('Invalid Method, only POST is supported! \nPlease send a POST with data in format {"Operation":1","data","Data-Gram!" }
} else {
res.end('Invalid request. Did you mean to POST to /postprocess? \n');
}
});
Next, in the Cloudflare dashboard, attach an Access policy to that hostname. This example uses the Service Token mode, which generates a client-specific token attached to each HTTP POST; other modes suit interactive browser workflows better.
function postProcess (postJson) {
const ServerVersion = "2.5.17"
if(postJson != null && 'operation' in postJson && 'data' in postJson){
var output
var operation = postJson['operation']
var data = postJson['data']
switch(operation){
case "1":
output = String(data).toLowerCase()
break
case "2":
d = data + "\n"
output = d + d + d
break
case "3":
output = ServerVersion
break
default:
output = "Invalid Operation"
}
return {'Output': output}
}
else{
return {'Error':'Invalid request, invalid JSON format'}
}
At this point, any Internet-connected device holding the correct token can POST to the service still running on the laptop — no VPN required. This delivers a significant security upgrade in minutes, and routes production traffic through the target-state API gateway, preparing the path for surgical traffic shifting later.

Lifting the Application to Workers
With traffic secured and flowing through Cloudflare, the application itself can move. Wrangler simplifies the process.
The Node.js HTTP module needs rewriting; the business logic does not. A minimal Workers HTTP handler would grab the POST body, parse JSON, call postProcess(), and return the result. However, for a realistic path — where real applications are more complex — the migration uses Wrangler's Router template as a robust starting framework.
wrangler generate post-process-workers https://github.com/cloudflare/worker-template-router
After initialization, only the account_id in wrangler.toml and a few lines in index.js need updating. The critical line pulls in the original logic module:
const postProcess = require('./postProcess.js')
Copying the legacy postProcess.js into the project directory tells Wrangler to bundle the untouched business logic into the Workers deployment.

Running wrangler publish puts the application on workers.dev for open testing. The test endpoint is publicly reachable (and can also be protected with Access if desired), so traffic can be validated from any device.

Seamless Traffic Cutover
The final migration step — shifting production traffic without interruption — is the one most teams dread. The earlier security work pays off here. Because production traffic already routes through the Cloudflare network to the legacy app via Argo Tunnel, the cutover requires zero changes to IP addresses, SSL configuration, or any client-facing properties.

The transition is a single wrangler publish after editing wrangler.toml to point at the production route. Cloudflare immediately sends production traffic to the new Workers application instead of the Argo Tunnel. The app emits a version header so easy verification via curl confirms which backend served the request.

Rollback is equally straightforward: either set wrangler.toml back to workers.dev-only mode and publish, or delete the route manually. Either action sends traffic back to the Argo Tunnel.
Practical Notes for Real Migrations
Real applications are rarely this simple. Multiple components with complex interactions each need individual handling. Argo Tunnel may remain in use to reach data stores or other out-of-network resources. Non-JavaScript modules might require WASM. In all of these cases, Wrangler keeps the operational burden manageable.
The exercise demonstrates the pattern: retrofit zero-trust access first for an immediate security win, then lift the application onto Workers, and finally shift traffic over the already-proxied path with a single publish. This sequence minimizes risk at each stage while steadily converging on the target architecture.



