Working with Xserver — FTP, .htaccess, and deploys that fail
Article three. The server is Xserver, for the deeply unglamorous reason that I already had an account. Getting GitHub to publish to it automatically, though, turned up a few things worth knowing.
The setup
When something is merged into main, GitHub Actions minifies the HTML, sends it to Xserver over FTP, and checks that the version string on the other end matches. All I do is say "merge it".
There are three destinations: the game itself, its preview environment, and the portal site. The game and its preview share one FTP account and differ only by folder; the portal is a separate domain, so it has its own account.
Things that caught me out, in no particular order
FTP just fails sometimes
This is the most common one. Roughly one deploy in a few fails with an FTP timeout. I've never found the cause, but re-running the failed job almost always fixes it. I used to investigate each time; now I treat it as transient and have the AI re-run the failed job when it happens. Three failures in a row means something is genuinely broken — that's the threshold.
A subdomain is a folder
Create a subdomain on Xserver and it makes a folder of that name inside the parent domain's public directory, then serves it as the document root. Which means game.example.com/ and www.example.com/game/ point at the same place. Not knowing that, I was confused for a while about why the same game appeared at two URLs.
FTP accounts can be scoped to that folder, so separating them for the game and the portal means work on one can't break the other.
A preview environment is essential
Pushing an AI's changes straight to production is frightening. So opening a PR now publishes to a preview URL automatically, and I check there before merging. The preview carries noindex so it never shows up in search.
I got this wrong once: the AI pushed a portal update straight to production, skipping the preview. It had assumed "the portal has no preview environment" and carried on, when in fact a preview workflow existed. A look at the repository would have shown that. I also said "just put it up", so I didn't stop it either. This is what happens when assumption arrives before verification — for AIs and for humans.
.htaccess is resolved per directory
I put a "redirect non-www to www" rule at the portal root, and it had no effect inside the game folder. When each folder has its own .htaccess, the deeper one wins. The same rule had to be added on the game side too.
You can't verify minified output with string matching
The HTML that reaches production is minified, so checking "does this variable name exist" fails. Verify by function name or structure instead, or embed a version string somewhere dedicated and check only that. The verification method has to be designed around minification from the start.
How it works today
| Task | Owner |
|---|---|
| Code changes and PRs | AI |
| Preview review | Me |
| Merge instruction | Me |
| Merge and production deploy | AI (after my go-ahead) |
| Re-running failed deploys | AI |
| FTP accounts, DNS, domain settings | Me (server control panel) |
What I wish I'd known
- FTP failures are usually transient. Build "re-run the failed job" into the process
- Subdomains are folders. Scope FTP accounts per folder
- Build the preview environment first. Never go straight to production
- Embed a marker (a version string) you can still verify after minification
Series: "I wish I'd known this from the start"
- Start with GitHub, whatever else you do
- Where I got stuck connecting GitHub
- Don't trust the AI's memory. Keep everything in GitHub
- Working with Xserver — FTP, .htaccess, and deploys that fail (this article)
- Always start from the facts — write the rules, then keep fixing them
- The parts that were genuinely hard, and what I did about them
- AdSense keeps rejecting my site
- Just write @claude in an Issue, and Claude Code gets to work
- Can you really hand game dev off to AI?
- Goodbye, ChatGPT — Banned Twice, Moved to Claude
- How I Get Claude to Make Pictures