Field Report ⑤: A two-day rescue off an end-of-support server

AI case studies

"It's working, so don't touch it." A system said to be off-limits like that, before anyone realized it, was still running at the center of the company as it was 10 years ago — many companies will recognize this. This time, we migrated a project-management system released in 2016 from a state where it wasstill exposed to the internet on top of an OS that reached end-of-support in 2020and in use, to a modern environment. Two working days.We carried over 3,623 tickets and 4.0 GB of attachments without a single loss.

"It's working, so we can't touch it" had gone on for 10 years.

The target was the company's project-management system (Redmine). Project progress, incident-response records, the history of exchanges with customers — 10 years of it had accumulated here. It was precisely a state ofcan't touch it because it can't be stopped.

But when we examined the contents, it was by no means a state that should be left alone.

ItemBefore migrationAfter migration
OSCentOS 6.10
(end-of-support in 2020)
AlmaLinux 10.1
ApplicationRedmine 3.2.1 (2016)Redmine 6.1.3
Runtime environmentRuby 2.2.4Ruby 3.3.10
DatabaseMySQL 5.7MariaDB 10.11
Web serverApache 2.2 + Passengernginx 1.26 + puma

6 years since the OS went out of support.Not a single fix for the vulnerabilities disclosed in that time had been applied. And it kept running in a state accessible from outside the company. Not "safe because it's working," but rather,"merely working, but not protected"is the accurate way to put it.

The real risk of legacy is not "one day it will break."
It's that, without breaking, only the defenses have stopped.

The success of the migration was decided by measurements taken before starting.

Before beginning the migration, we pinned down the current state in numbers. This, as it turned out, greatly influenced the effort required.

  • Database 38 MB / attachments 4.0 GB
  • 20–30 requests per day =actively in use
  • Extension plugins: zero. Custom design: none

That last line was decisive. What causes the most trouble in this kind of migration iswhether the extensions added later will work on the new version. If they don't, you're left with three choices — "find a replacement," "give up the feature," or "fix it yourself" — and weeks vanish there. This time, that obstacle didn't exist from the start.

Based on these measurements, we settled on three policies before starting.

1. We stopped upgrading one version at a time

Initially we estimated on the assumption of stepping up gradually, 3.2 → 4.2 → 5.1 → 6.x. But when we tested it, we found that we couldjump over 10 years' worth in one go and update directly. The effort we had assumed became entirely unnecessary.

2. We did not change the database product

There was a plan to move to a different database product to align operations with other systems. But because the source data format differs, that would require inserting a conversion step.We chose not to bring the risk of conversion into "data that's hard to notice if it disappears" — 3,623 tickets and 6,071 history entries.We prioritized not losing a single record over unifying operations.

3. We did not upgrade the existing server directly

We prepared a separate new server and made the switchover just a matter of rewriting one line of the entry-point configuration.If it fails, revert one line and it's back to normal — to maintain that state right to the end.

What ate up the time was not Redmine.

Here's where the real work is. Lining up the problems we hit during the migration, a clear pattern emerged.

The old server and the new server couldn't even communicate in the first placeOSThe latest OS rejects the previous generation's encryption schemes as insecure.There was no way to move the 4 GB of files, which is where the migration began. We handled it by creating a temporary transfer channel on the old-server side and closing it when done.
10 years of version upgrades went through in one passApplicationBecause Redmine retains all of its past update procedures, we could go straight to the latest version without passing through intermediate versions. 57 update operations completed without error.It only worked because there were zero extensions — a shortcut.
A required component isn't included in the standard configurationApplicationThe component used to integrate with the web server isn't bundled with Redmine itself. We solved it by adding it to the official extension slot, which won't be overwritten by future version upgrades.
Even with permissions fully opened, it's rejected with "permission denied"OSThe OS security mechanism was blocking communication at a layer separate from file permissions.no amount of loosening permission settings solves it — a textbook case. We addressed it by changing the connection method itself.
"Page not found" the instant you log inCommunication pathTwo servers along the path were overwriting the same information back and forth, and the information for encrypted communication was being lost. The application was fine;only the forwarding destination didn't exist — a hard-to-spot failure.
Notification emails can't be sent due to a certificate errorEncryptionBecause it connected by IP address, it didn't match the name on the certificate.Rather than disabling verification to work around it, we defined a name that matches the certificate and resolved it with verification left enabled.
There's no standard feature to stop password brute-forcingApplicationRedmine has no standard "lock after a set number of failures" feature. We reinforced it by limiting the number of attempts on the web-server side.Block in the wrong place and every employee gets locked out, so a configuration that correctly identifies the actual source of access is a prerequisite.
The source-code integration depended on the old serverExternal integrationIt was configured to reference files inside the old server directly, so migrating would break all of it. We built a mechanism to automatically replicate from the in-house Git server, moving to a configuration thatupdates every 15 minutes. The integration, which had been frozen, was actually revived.

Of the eight,only three stemmed from Redmine itself. And one of those was a pleasant miscalculation — "easier than expected." In practice, only two gave us any real trouble.

The rest were generational differences in the OS, the security mechanism, the communication path, encryption, and integration with external systems —all of them outside the application.

What eats up the effort in a legacy migration is not the application itself.
It's the "differences in the surrounding environment" built up over 10 years.

Never let go of "revertible in one line" until the very end.

The switchover is just pointing the entry-point server's forwarding destination at the new server. Reverting is also one line. Andwe left the old environment running rather than shutting it down.

On switchover day, a "running old environment" and a "running new environment" coexisted, and we kept a state where we could move between either with one line. After leaving it for a few days and confirming there were no problems, we shut down the old side.

Splitting the "switchover" and the "shutdown of the old environment" onto different days.That alone changes the judgment calls you make on the day. If you know you can revert should anything happen, there's no need to pile on stopgap fixes in a panic with the cause still unknown. What costs the most in migration work ispanicking from a point of no return.

We didn't stop at migrating; we filled in 10 years' worth of gaps.

In the new environment, we simultaneously put in place what the old environment lacked.

  • Daily backups (7 generations) — the old environment had no backup mechanism at all
  • Recording the source of access — before migration, all access looked identical, and there was no way to trace who was coming from where
  • Limiting login attempts — since there's no standard feature, reinforced on the web-server side
  • Automatic updating of the source-code integration — an integration that had effectively been frozen now updates every 15 minutes

The data we carried over is as follows. We confirmed that the counts match exactly between the old and new environments.

21Projects
3,623Tickets
6,071Response history
4.0GBAttachments (0 lost)

Integration from external systems also works as-is, with no configuration changes.From the users' point of view, only the login screen got a new look — an ideal landing for a migration.

If "we have the same kind of server too."

The reasons this migration finished in such a short time come down to three.

  • There were no extensions installed — that's why we could jump over 10 years at once
  • We measured before starting — the basis for decisions was laid out in numbers
  • We kept it revertible — we didn't have to gamble on switchover day

Put another way,all three of these can be confirmed before you start."A system that can't be stopped is running on an end-of-support server." If that rings a bell, start by talking to us about grasping the current state in numbers. Whether it can be migrated can usually be determined by investigating.

We help you migrate and modernize business systems that keep running on end-of-support OSes and old frameworks.
A survey of the current state (version, data volume, presence of extensions, exposure) is free. Together with legacy system modernization (the AI Re: Platform / AIR Platform for short),Get in touch today.

Contact form(please add a note saying "Requesting a consultation on legacy migration") / info@flagship-ai.jp

Series: "Legacy to AI — Field Reports"

* The figures in this article are measured values as of August 2026. The time required and the work involved in a migration vary depending on the system's configuration and whether extensions are present.

Category:

Tags:

🌐 English