Playbook ~11 min read By The Compiler Whisperer · October 2026

Running an Access Database on a Modern Stack Without Rewriting It

The default advice for an Access application is rewrite it. Move the tables to a real database, replace the forms with a web front end, and port the VBA to Python or C#. It is clean, it is what everyone suggests, and for a small business it is usually the wrong first move — because the rewrite re-implements the one thing you cannot afford to get wrong, which is the business logic that took twenty years to accumulate and now lives in someone's macros.

There is a second option that is less talked about because it is less dramatic: keep the front end, replace only the weak part, and add a layer where you need reach. An Access "application" is really two things glued together — a file database (the Jet engine and the .accdb), and the UI plus VBA that sits on top. The file database is the weak link: one file, weak concurrency, no real backup or high availability, and a driver model that stops scaling around a dozen users. The UI and the VBA are the asset. So you do not have to rewrite the asset to fix the weak part.

This article is the playbook for that: three moves you can make in escalating order, each of which leaves the Access forms and VBA you have intact, plus the honest limits of each — because "without rewriting" has a ceiling, and you should know where it is before you commit.

First: name the two halves of your Access system

Almost every "Access app" you will find is a single .accdb or .mdb file that contains all of this at once:

HalfWhat is in itIs it the weak part or the asset?
The file database (Jet engine)Tables, indexes, relationships — the data store itselfWeak part. Single file, file-lock based, limited concurrent writers, hard to back up safely while in use
The front end + logicForms, reports, VBA modules, macros, the AutoExec startup chainAsset. This is where two decades of business rules live. Rewriting it is where rewrites go to die

"Modernize without rewriting" means: leave the asset alone, and do all of your work on the weak part and in the layer above it. That single re-framing is what makes the whole approach work, and it is why you can keep the exact forms your staff already know how to use.

THE THREE MOVES (escalating — each one is optional and stacks on the last) MOVE 0 HARDEN IN PLACE keep the file, kill the "it's on a desktop" risk → split the database, real backup, tested restore, one proper machine MOVE 1 MOVE THE BACKEND replace the .accdb file with a managed database → link the SAME Access front end to PostgreSQL or SQL Server → forms and VBA unchanged; only the data source moves MOVE 2 WRAP IN A SERVICE add a thin layer ON TOP so web/phone/reach works → a small service calls the VBA (via COM) or reads the managed DB → Access becomes a "business-logic service"; the UI is now optional

You do not have to do all three. Move 0 is a day or two and removes the biggest risk (a database living in someone's Downloads folder). Move 1 is where the real modernization happens. Move 2 is only for when you need reach that a desktop app cannot give you. Pick the highest move you can actually complete — an abandoned Move 2 is worse than a finished Move 1.

Move 0 — Harden in place (do this first, no matter what)

Before any architecture, fix the fact that the database is a file in an unmanaged location. This changes no logic and rewrites nothing, but it is the difference between "we can restore it" and "we found out on the 14th that we can't."

Move 0 does not make the system scalable. It makes it recoverable, and it gives you the stable base that Moves 1 and 2 are built on. If you do nothing else this year, do this.

Move 1 — Replace the file with a managed database, keep the front end

This is the move that most people skip because they assume it requires a rewrite. It does not, as long as your front end talks to its data through tables rather than through a hard-coded file path. And that is exactly what a split database gives you: the Access forms read and write tables, and a table does not care whether it lives in a local Jet file or in PostgreSQL or SQL Server — as long as the front end is linked to the right place.

The mechanics:

  1. Export the schema. Read your table definitions — names, field types, indexes, relationships, and any computed fields. Access will generate the DDL for you, or you can read it from the Jet catalog. You are not writing this by hand; you are capturing it.
  2. Import into a managed database. Create the same tables in PostgreSQL or SQL Server (whichever you will actually operate — both are fine, and neither is the only option). Import the data. At this point you have a managed, backed-up, high-availability copy of your data with the same shape.
  3. Re-point the front end. In the Access front end, remove the old linked tables and link to the new database instead. For most tables this is Database Tools → Linked Table Manager → change the source. The forms, reports, and VBA do not know — and do not need to know — where the rows come from.
  4. Test against pinned data, not against memory. Take a known snapshot of the old back end. Run the front end against the new back end. Compare. The numbers must be identical, because the logic did not change — only the storage did. If they differ, you found a data-type mapping bug, and finding it now is free compared to finding it in production.
  5. Run old and new in parallel for one real cycle. The old file keeps being the system of record. The new one runs alongside. Diff quietly. Then cut over, and keep the old file warm for 90 days exactly as I do in every other migration on this site.

After Move 1, you have done something genuinely important without touching a line of VBA: your data now lives on a database that has real backups, real concurrency, real restore points, and does not depend on a single file not being corrupted. Your staff still open the same forms. The machine that used to host a fragile file now hosts a managed service.

WHAT CHANGED AND WHAT DID NOT DID NOT CHANGE DID CHANGE ─────────────────── ─────────────────────────── The Access forms The tables live in a managed RDBMS The VBA modules (PostgreSQL / SQL Server) The reports Backups are real and restorable How staff use it Concurrent writers are now real The business logic The Jet file is gone from the data path The host is a service, not a desktop file

One honest caveat, because people trip on it: Move 1 changes the storage engine, not the concurrency model of the front ends. If your real problem was "thirty people writing at the same time," a managed back end helps a lot — it handles the writes correctly now — but it does not turn a form-based desktop app into a web app. That is Move 2's job. Do not let Move 1 be sold to you as if it solves reach.

Move 2 — Add a service layer on top (reach, without touching the core)

This is the move you only do when you need it: when someone has to reach the system from a browser, a phone, or another system, and a desktop Access window will not do. The key idea is add a thin layer above the logic — do not rewrite the logic. The Access app (or the managed database it reads from) becomes a service that something else calls.

One boundary, said out loud, because it is where this article meets the VB6 report article: if what you need to reach is a report — a query plus a layout — it can usually be expressed in SQL, and the cleaner path is a two-layer rebuild (a SQL data layer plus a presentation layer), not a COM call. COM-wrapping the VBA is for logic that lives inside VBA and is not a query — the pricing rule, the posting logic, the thing you would otherwise have to rewrite. Same goal (reach, without rewriting), different door. Pick the door by where the value actually sits: in the data, or in the code.

There are two honest ways to do this, and they differ in what they can reach:

Wrapper styleWhat it callsGood forWatch out for
Read the managed DB directlyThe PostgreSQL/SQL Server from Move 1Any query or read that is expressed as SQL — reports, lookups, sync to another systemIt bypasses the VBA. If a "value" is computed inside a VBA function, the raw DB row is not the same as what the form shows
Call the VBA via COMThe Access app itself, through the COM automation interfaceLogic that lives in VBA and you are not touching — the app is now a callable serviceThe host is a Windows machine with Access installed; the call is synchronous and single-threaded, so it is a service, not a web server

The COM route is the one that genuinely "doesn't rewrite" anything, because it treats your Access app as a black box with a function in and a value out. A minimal Python call looks like this:

import win32com.client as win32 app = win32.Dispatch("Access.Application") app.OpenCurrentDatabase(r"\\fileserver\apps\front_end.accdb") try: # Call a VBA function you already have — you did not write or change it result = app.Run("modPricing.CalcCustomerTotal", "C-1042") # ... finally: app.CloseCurrentDatabase() # That is the whole wrapper. The business logic is the VBA you # already have. You added a door to it; you did not rebuild the house.

Two things to be honest about with Move 2. First, Access and VBA are Windows-only, so a COM wrapper has to run on a Windows host — a Windows managed instance is the natural fit (a Cloudways-managed Windows instance ↗ gives you patching, backups, and monitoring without owning the box). Affiliate link — I may earn a commission if you sign up; you pay the same price either way. Do not plan to run Access on a cheap Linux box; it will not, and the plan is wrong, not the hardware. Second, the COM wrapper is a service, not a web framework. It is single-threaded and synchronous. It is perfect for "call the pricing logic from another internal system," and it is not the right thing for "serve 200 customers a checkout page." For that you need a real application, and that is a rewrite — which is the ceiling "without rewriting" runs into.

Where "without rewriting" stops

Say the limits out loud, because a plan that ignores them fails quietly:

None of these make the approach wrong — they make it scoped. "Without rewriting" is a legitimate strategy with a known ceiling, and knowing the ceiling is what makes it a strategy instead of a hope.

Risks that actually bite, and the cheap mitigations

RiskHow it shows upCheap mitigation
Concurrency illusionMove 1 done, yet "two people still can't save at once"Split the database first (Move 0). The back end must be one file/DB; the front ends are the many
Bitness mismatch"Linked tables work on my PC, not on the server" — 32-bit Office pointing at a 64-bit driverPin the Office/Jet bitness in Move 0 and install the matching ODBC/Jet driver on the host
Data-type drift on importA "Memo" field, a Currency rounding, or a Date behaves differently in PostgreSQL/SQL ServerCompare front-end output to the pinned snapshot (Move 1, step 4). Fix the mapping, not the logic
Hidden startup dependenciesAn AutoExec macro or startup form that only ran because of a local path or a local network nameInventory the startup chain before you move; re-create the dependency on the new host
Wrapper exposes what the form hidMove 2's service gets called more than the desktop app ever was, and a slow VBA path now blocks a callerTreat the COM service as single-threaded; add timeouts; keep it an internal service, not a public one
The "it works on one machine" trapEverything fine until the second machine, or the first rebootMove 0 is specifically about making the environment portable and documented. Do not skip it to save a day

How to choose — the honest decision

Your situationDo
Access file on a desktop, no real backup, 1–5 usersMove 0 only. That may be the whole job. Do not over-build.
5–20 users, shared back end, you want real backup/HA and to get off a fragile fileMove 0 + Move 1. The sweet spot for most small businesses. Front end untouched.
You also need web/phone/reach or to feed another systemMove 0 + 1 + 2. And only then — Move 2 needs Moves 0 and 1 underneath it to be stable
The VBA itself is the problem (huge, untrusted, the real reason for the project)Wrapping will not fix this. That is a rewrite of the logic, scoped carefully — a different project, and I would plan it as one

FAQ

Will my staff notice anything?

For Move 0 and Move 1, ideally nothing: they open the same forms, run the same reports, and the numbers are identical. The changes are underneath — where the data lives and how it is backed up. Move 2 is the one that adds new capability (reach from a browser or another system), and that is a new thing, not a change to the old one.

Can I do Move 1 without stopping the business?

You do the build on the side, then cut over at a boundary. The old back end stays live and is the system of record until the new one has run one full real cycle in parallel and matched. There is no moment where the business is "down" waiting for a rewrite — that is the point of doing this underneath a system that keeps running.

Which managed database — PostgreSQL or SQL Server?

Both work for this. Choose by what you will actually operate and what your data is closest to. If your Access app leans on SQL-Server-isms or your team already runs SQL Server, SQL Server is the path of least resistance. If you want open-source, portability, and lower cost, PostgreSQL is strong. The front end does not care; you do, so pick on operating grounds, not on which is "more modern."

Why can't the wrapper run on a cheap Linux server?

Because the thing it wraps — Access and VBA — only runs on Windows. A Python service that reads a managed database could run anywhere, but the moment it calls the VBA via COM, it needs a Windows host with Access installed. Plan for that, or use the read-the-DB style for the parts that are pure SQL.

Is this a replacement for the "rewrite it" people?

It is the alternative for the common case, where the logic is sound and the risk is in the file and the reach. If your real problem is the logic, then yes — you are doing a rewrite, and this article is not it. Read Retiring a VB6/VBA Application and Microsoft Access Alternatives to compare the full-rewrite option honestly.

Summary

An Access application is two halves: a file database that is weak, and a front end plus VBA that is the asset. "Modernize without rewriting" is not a slogan — it is the discipline of doing all your work on the weak half and in the layer above it, and leaving the asset alone. Move 0 makes it recoverable (split it, back it up, test the restore). Move 1 replaces the fragile file with a managed database and re-points the same front end to it — your staff see no change, but the data now has real backups and real concurrency. Move 2 adds a thin service on top for reach, calling the VBA through COM on a Windows host — and it has a ceiling, because the UI is still desktop-shaped and the host is still Windows. Know that ceiling, and "without rewriting" stops being a hope and becomes a plan with a known edge.

Where to go next

Disclosure: some links above are affiliate links (marked where they appear). They never cost you more, and they never influence the recommendation. Full details on the affiliate disclosure page. This article is general engineering information, not professional advice.