PEERPLAY MENU
Download PeerPlay
Home / Blog / My Blog Deploy Failed 11 Times in a Row. Then I Dropped FTP.

My Blog Deploy Failed 11 Times in a Row. Then I Dropped FTP.

Short answer

PeerPlay's blog deployed from GitHub Actions to shared hosting over FTP. On October 14, 2026 one deploy timed out 11 times in a row and passed on attempt 12. That evening the site moved to Firebase Hosting on peerplayapp.com. The lesson: repeated deploy failures are a signal, and files that live only on the server are a trap.

On October 14, 2026 I merged a new blog post at 05:19 in the morning. It went live at 07:05. In between, the deploy failed 11 times in a row, and not once was the problem in my code.

This is a small story about a boring piece of infrastructure, but it changed where PeerPlay's website lives, and it surfaced a mistake I had been carrying for months without noticing.

How the blog was deployed

PeerPlay's site and blog are plain static HTML. A Node script turns each post into a page and rebuilds the index, sitemap and feed. Merging to the main branch triggers a GitHub Actions workflow, and that workflow uploaded the files over FTP to shared cPanel hosting, using a popular FTP deploy action.

It is the setup a lot of solo developers end up with. The hosting was already paid for, FTP is everywhere, and for a long time it simply worked.

The first warning: October 12

Two days earlier, a deploy had failed with "Timeout when trying to open data connection". I re-ran it and got the same error. A few hours later a third attempt went through. At the time it looked like one bad morning on shared hosting.

Looking back, that was the moment to ask a better question. Not "how do I get this deploy through", but "why does my publishing step depend on a connection I don't control and can't debug?"

October 14: eleven failures

The next deploy failed at the upload step within a minute, with "Timeout (control socket)". Two quick re-runs failed too, the second with the same data connection timeout as two days earlier. Checkout worked, the build worked, and the FTP server simply did not finish the conversation with the GitHub runner.

I set up a loop that re-ran the workflow every 10 minutes. Attempts four through eleven all failed. Attempt twelve passed at 07:05, about an hour and 45 minutes after the merge. I never pinned down the root cause. It could be the host, the network path from GitHub's runners, or FTP's passive data connections, which open a second connection on a random port and are easy for a firewall to drop. From the outside, every one of those looks identical: a timeout.

That evening: off FTP

The same evening, the site moved to Firebase Hosting on its own domain, peerplayapp.com. PeerPlay's app already runs on Firebase, so the GitHub workflow now deploys to Firebase Hosting instead of uploading over FTP. The first deploy on the new setup went through on the first try.

The move was mostly mechanical. Every canonical URL, Open Graph URL, sitemap entry, feed link and share link had to switch to the new domain, so all 52 posts were re-rendered. The cache headers and the 301 redirects for merged posts, which used to live in an .htaccess file, moved into the Firebase config. For the old domain there is a single .htaccess redirect that sends every path to the same path on the new domain, so old links and search results keep working.

The trap I found on the way

Two URLs, the privacy policy page and the account deletion page, existed only on the server. Their content lived in the repository under a different path, but those exact files did not, and every FTP deploy had simply left them alone.

For an Android app that is not a small detail. Google Play expects a working privacy policy link, and an app that lets people create accounts needs a way to request deletion. If I had pointed a fresh deploy at an empty host without noticing, both links would have broken. Moving hosts forced me to look, and now both pages are in the repo with everything else.

What I would tell another solo developer

If a deploy fails for a reason outside your code more than once, treat it as a signal, not bad luck. Retrying works, but every retry is time you are not spending on the product. And before you ever move hosts, list what actually lives on your server and compare it with your repository. The files that exist only in production are the ones that will hurt you.

FAQ

What does 'Timeout when trying to open data connection' mean in an FTP deploy?

The FTP control connection worked, but the separate data connection used for the upload could not be opened in time. In passive mode that connection goes to a random port, so firewalls, the host or the network path can block it.

Should I just retry a failed FTP deploy from GitHub Actions?

A retry often works for a one-off timeout. If it keeps happening, consider a deploy method that does not depend on FTP, such as your host's Git deploy, SFTP or a static hosting service.

How do I keep old links working after moving a site to a new domain?

Add a 301 redirect on the old domain that sends every path to the same path on the new one, and update canonical URLs, sitemaps and feeds on the new site.

What should I check before moving a static site to new hosting?

Compare what is on the server with what is in your repository. Pages uploaded by hand, such as a privacy policy, are easy to lose in a move.

Ready to Pass Your Closed Testing Requirement?

Join PeerPlay's peer-to-peer network and get your 12 verified testers for 14 days - free.

Download PeerPlay