I built a reverse proxy. It works. For my use case it beat nginx. I’m deleting all of it.
Not because it’s bad. It does the job and the numbers came out better than I expected. I’m deleting it because somewhere in the last couple of weeks I quietly stopped fixing it and started bolting things onto it, and it took me embarrassingly long to notice there was a difference.
The thing is called Wicket. I built it on Pingora, Cloudflare’s Rust proxy framework, because Pingora is excellent and I wanted it sitting at my front door taking client connections. That was the mistake, and it’s a subtle one: for what I needed, Pingora wasn’t the front door. It was the origin connector. It’s the thing Cloudflare points at the messy long tail of other people’s servers, not the thing that faces the internet. I’d grabbed the right tool and put it in the wrong slot. Everything I then needed at the edge, dynamic SNI, QUIC, listeners I could change without a restart, was me working against that wrong placement instead of with the right one.
And here’s the part I actually want to write down, the part for anyone earlier in their career than me.
The reason I didn’t catch it sooner is not that I was lazy or didn’t care. It’s the opposite. I cared a lot. I worked hard. And every single change I made worked. That’s the trap, and it’s worth learning to see, because nothing about it feels like a mistake while you’re in it.
There’s a line in most projects where the work changes character. Before the line, you’re building the right thing and your changes make it more correct. After the line, the core idea is slightly wrong, and your changes are propping it up. You’re adding a flag to handle the case the wrong abstraction can’t, then a special path around the flag, then a workaround for the special path. The tell is that it keeps working. Every bolt-on is rewarded the instant you make it. The benchmark goes green, the feature lands, you feel productive. There is no moment where the code turns around and says “by the way, you should have started over three weeks ago.” The feedback is positive the entire way down, which is exactly why momentum is dangerous: it doesn’t distract you from the hard question, it answers it for you, falsely, with “look, it runs.”
That’s what “it works” did to me. I treated it as proof I was on the right track, when all it ever proved was that this particular wrong idea hadn’t broken yet. Working and correct are not the same property. You can have one without the other for a surprisingly long time, and the more effort you’ve poured in, the more the working version feels like something you’re obligated to defend.
I learned the shape of this from a boss I had as a co-op, Marc Morin. He told the team something I’ve never quite forgotten, roughly: you’re not going to get it right on the first pass. The second will be better. By the third you might get it right. I always loved the “might.” It’s not a motivational poster. It’s someone who’s been humbled enough times to refuse the clean ending. The first version is how you discover what the problem actually is. You couldn’t have known going in. That’s not failure, that’s the job.
What’s changed since Marc said that is the price. Back then a pass cost weeks, so starting over was a genuine sacrifice and “I’ve put too much into this to throw it away” was at least an honest excuse. Now I can stand up a working pass in days, sometimes with an afternoon of prompting. Producing the work got cheap. That means the old excuse holds less weight than it ever has, and the actual scarce skill is no longer typing the thing, it’s being willing to walk away from a version that’s fine. Most people can’t. The work feels like a sunk cost they have to honor. It isn’t. The days I spent on Wicket weren’t wasted, because the thing I’m keeping was never the code. It’s that I now understand exactly where Pingora belongs and why an edge needs something else. That understanding is the asset. The code was just the receipt.
So if you’ve built something you’re proud of getting working, here’s the only question I’d ask you to sit with: are you still fixing it, or have you started propping it up? If every recent change has been a workaround for the last workaround, that’s the bolt-on smell, and starting over is probably the higher-value move, not the wasteful one. You don’t lose the work. You keep the understanding and pay only to retype, which is the cheap part now.
One honest caveat so you don’t take this too far: not everything gets a cheap restart. A data model, an API other people build against, a wire format, those are expensive to undo and worth getting slow and careful about up front. The skill isn’t “rewrite everything constantly.” It’s knowing the few decisions you don’t get a cheap second shot at, getting those as right as you can, and then being completely unsentimental about restarting everything else.
I’m going to go delete Wicket now. Don’t cry over spilt milk. The milk was never the point.
Disclaimer: I hate writing. I’m using AI to get my ideas onto paper. The opinions, experience, and numbers are mine. The grammar is not.