Turning a Meraki MX64 into OpenWrt, against its will

24.03.2026

I bought a Meraki MX64 for the same reason many people do: it was cheap, overbuilt, and looked like a piece of serious network gear that deserved a second life.

What I did not buy was a clean, modern, well-documented OpenWrt experience.

This post is about taking a used MX64, getting OpenWrt onto it, fighting through serial weirdness, outdated repo artifacts, naming mismatches, missing files, ambiguous documentation, bootloader replacements, USB boot confusion, and finally ending up with a working OpenWrt box that now sits as the hardened inner boundary of my network, with a separate Debian-based Stora NAS behind it.

It worked in the end.

It was not elegant.

Why even bother ¯_(ツ)_/¯

The Meraki MX64 is one of those devices that feels like it should be easy to repurpose.

It has:

  • decent hardware for routing/switching
  • multiple Ethernet ports
  • solid build quality
  • the kind of industrial vibe that makes you want to save it from e-waste

The problem is that Meraki hardware is not built for you. It is built for Meraki’s own stack, their assumptions, their boot flow, their firmware, their management world. The box is happy only when it is doing exactly what Cisco Meraki intended.

The moment you try to make it yours, it becomes a puzzle box.

The real starting point: this is not a “flash image and done” device

This is the first misunderstanding I had.

I assumed the process would look something like:

  1. get OpenWrt image
  2. TFTP or USB boot it
  3. sysupgrade
  4. done

That is not what this box wants.

The MX64 install path is more like:

  1. get serial access
  2. get into the right recovery/diagnostic environment
  3. inspect the exact hardware state
  4. determine if boot flash is locked
  5. determine if it is A0 or not
  6. replace stock U-Boot with a custom one
  7. boot the right temporary image
  8. only then install the actual OpenWrt system (compiled by hands)

So before you even get to OpenWrt, you are really doing bootloader archaeology.

Serial access: the first fight

The board exposes a 4-pin serial header.

Eventually the correct mapping was:

  • pin 1: 3.3V, do not connect
  • pin 2: board TX -> adapter RX
  • pin 3: board RX -> adapter TX
  • pin 4: GND

In theory, that sounds easy.

In practice, it was one of those stupid sessions where you start doubting everything:

  • wrong header?
  • wrong pin order?
  • wrong baud?
  • dead adapter?
  • bad jumper contact?
  • wrong voltage?
  • broken board?

At first, nothing showed up. Then later it showed up. Then it disappeared. Then I got partial boot logs. Then it worked only when the wires were physically held a certain way. Then the serial adapter would reconnect and disappear again.

Classic garbage-tier Dupont cable reality.

Eventually I got clean output at 115200 8N1, which was the first real sign that this project was not totally cursed.

First useful success: stock Meraki boot logs

Once serial finally worked, the MX64 booted into stock Meraki firmware and printed enough to confirm important details:

  • it really was an MX64
  • serial settings were correct
  • the box was alive
  • reset button events were visible
  • NAND partitions were visible
  • there was still a path forward

That part matters because it tells you the device is not bricked, not dead, and not silently broken in some strange hardware way.

It also tells you the “problem” is now software and procedure, which is much better than dead silicon.

The second misunderstanding: the <Meraki> prompt is not a shell

At one point, the box booted into a prompt that looked promising:

<Meraki>

Naturally, I tried things like:

  • help
  • ?
  • sh
  • busybox sh
  • ash

Every one of them came back as some variation of “unrecognized command.”

That was another little trap. Seeing a prompt does not mean you have a usable shell. It just means Meraki is willing to display a prompt-shaped object on your screen.

The actual useful environment came later, in the diagnostic BusyBox shell.

Diagnostic mode: finally something useful

By holding reset during boot/after boot/while boot etc., I eventually got into the BusyBox-based diagnostic environment.

This was the first moment the process stopped feeling fake and started feeling actionable.

From there I could check the three critical values:

  • whether the boot flash was read-only
  • what SoC revision the unit had
  • what size the boot partition was

For my unit, the important facts were:

  • flash was locked
  • SoC was regular MX64, not A0
  • boot partition size was 1 MiB

That determined the install branch.

This matters because the install path is not the same across all MX64-like cases. If you skip those checks, you are gambling.

The third misunderstanding: “the repo name must match the model”

This one wasted a stupid amount of time.

There are clayface repos involved in the older MX64 bootloader process. And because the naming is similar, it is very easy to land in the wrong place and then spend hours staring at files that look almost right.

That happened to me.

At different points I was bouncing between:

  • MX64 repo
  • MX65 repo
  • older bcm5862x artifacts
  • newer bcm53xx artifacts
  • repo files
  • official OpenWrt files
  • wiki references to external sources
  • image names that looked almost identical except for one chunk in the middle

The worst part is that the names are close enough that your brain keeps telling you, “this is probably the one.”

It is often not the one.

Replacing U-Boot: the point of no return

The actual first major irreversible step was replacing the stock bootloader.

Because my boot flash was locked, that was not just a simple write. It involved:

  • getting the custom mtd utility
  • loading mtd-rw.ko
  • making the boot partition writable
  • writing uboot_mx64

That part is unnerving because once you cross that line, you are no longer “just experimenting”. You are now actively reauthoring how the box boots.

And this kernel module parameter (funniest and most honest names), make me nervous:

insmod mtd-rw.ko i_want_a_brick=1

OpenWrt installer: “Are you sure?”
You: “yes”
Module: “Not enough. Say the words.”
You: i_want_a_brick=1

The good news is that once the write completed cleanly, that was one of the biggest milestones of the whole process.

The bad news is that it did not make the rest easy.

It just unlocked a new category of confusion.

U-Boot prompt chaos and serial noise

After replacing the bootloader, the box dropped into a custom U-Boot prompt.

Which would have been nice, except the serial line was flaky enough that U-Boot frequently interpreted garbage as input.

So what I got was:

  • weird characters
  • unknown command errors
  • autoboot interruption
  • manual prompt instead of smooth USB boot

This created another layer of doubt because now I had to figure out whether I had a bad image, a bad bootloader, or just garbage coming over serial at the wrong time.

It turned out to be mostly the last one.

Serial noise is enough to ruin what would otherwise be a clean autoboot.

The fourth misunderstanding: old boot artifacts vs actual OpenWrt install images

This is the part that cost me hours.

There are older clayface repo images used for bootloader-related steps, with names like:

  • openwrt-bcm5862x-generic-meraki_mx64-initramfs-kernel.bin

And then there are the current OpenWrt install-stage images, with names like:

  • openwrt-bcm53xx-generic-meraki_mx64-initramfs.bin
  • openwrt-bcm53xx-generic-meraki_mx64-squashfs.sysupgrade.bin

The documentation does not make this distinction feel as sharp as it really is when you are sleep-deprived and juggling USBs, serial, static IPs, and multiple repos.

So I spent a lot of time asking some variation of:

  • do I rename this?
  • do I use the repo image?
  • where to get official one?
  • why does the wiki say one thing but the repo contains another?
  • why can I find the sysupgrade file but not the initramfs file?
  • why is the expected filename not obviously available where I thought it would be?

That last one was especially bad.

The missing initramfs rabbit hole

The sysupgrade image was easy enough to get.

The actual initramfs image I needed for the install stage was not.

This is where the process became deeply annoying.

The documentation referred to:

openwrt-bcm53xx-generic-meraki_mx64-initramfs.bin

But there was no neat obvious “here is that exact file” path that matched what I expected. The public sources, repo contents, and wiki wording did not line up cleanly enough to make this feel straightforward.

At some point, I realized I had spent far too long trying to confirm whether a prebuilt file existed in the shape I wanted.

After few minutes of forum scrolling.
I stopped searching.

I realize that I just should compile it myself.

That was the correct move.

Compiling the actual initramfs myself

This ended up being one of the most useful decisions in the whole process, because it replaced ambiguity with control.

In OpenWrt menuconfig, the important choices were:

  • Target System: Broadcom BCM47xx/53xx (ARM)
  • Subtarget: Generic
  • Target Profile: Cisco Meraki MX64
  • Target Images: enable ramdisk and squashfs

Then build.

After the build finished, I finally had the exact artifacts I wanted:

  • openwrt-bcm53xx-generic-meraki_mx64-initramfs.bin
  • openwrt-bcm53xx-generic-meraki_mx64-squashfs.sysupgrade.bin

That was the moment the entire process became sane again.

The lesson here is simple:

If the naming, download location, and documentation feel inconsistent enough that you are no longer sure what you are holding, build the damn file yourself.

This was also the moment I understood Gentoo users a little better. (๑>؂•̀๑)

The time spent compiling was far less painful than the time spent second-guessing artifact names.

USB boot: finally real progress

With the freshly built bcm53xx initramfs on the USB and the custom U-Boot already in place, the MX64 finally did what it was supposed to do:

  • white blink pattern
  • USB boot
  • temporary OpenWrt environment

This stage still had some confusion because not all initramfs environments are equal. In my case, I got a booted OpenWrt initramfs, but it did not expose USB storage the way I initially expected for the final stage.

That meant I could not just mount the USB and sysupgrade from there.

So the solution became:

  • boot initramfs
  • copy sysupgrade image over the network to /tmp
  • run sysupgrade

That worked.

And when the SSH session got killed during sysupgrade, that was one of the few moments in this process where “connection closed” was actually a good sign. ◟(‘ﮧ’ ◟ )

Reboot: OpenWrt is real now

After sysupgrade and reboot, OpenWrt came back up from flash.

Not initramfs.
Not recovery.
Not Meraki.
Not U-Boot shell.
Actual installed OpenWrt.

That is the point where the project stops being a flashing exercise and becomes a real network design problem.

Now the box had:

  • WAN toward the main router
  • LAN on a new subnet
  • functioning OpenWrt routing/firewall
  • a stable SSH endpoint
  • config backup
  • a real place in the network

That was the first time the MX64 felt like a tool instead of a hostage situation.

Turning it into the hardened inner boundary

I wanted the MX64 to become the more controlled, more hardened part of my network.

So instead of replacing my main router outright, I used a cleaner layered design:

  • main router remains upstream
  • MX64 WAN plugs into main router LAN
  • MX64 LAN becomes its own subnet
  • sensitive devices go behind the MX64

That gave me a private interior segment that I could manage separately.

I moved the MX64 LAN to:

192.168.50.0/24

with the router at:

192.168.50.1

That way the MX64 became the gatekeeper for devices behind it, rather than just another random switch-shaped object on the old flat network.

Then came the Stora

This should have been easy.

It was not.

The Stora is a Debian-based NAS-like box I had previously configured with a hardcoded IP:

  • Debian 11
  • static IP 192.168.0.165
  • SSH key only
  • root SSH disabled
  • password auth disabled
  • firewall allowing LAN-only SSH

And… I successfully forgot about last one and all turned out to nightmare. ༎ຶ‿༎ຶ

The fifth misunderstanding: “same cable, new subnet, should be fine”

I plugged the Stora behind the MX64 and expected that, after changing the IP, I would be able to SSH into it normally.

Instead:

  • the MX64 could see it at layer 2
  • ARP worked
  • MAC address was visible
  • the link was up
  • but ping and SSH from the new subnet did not work

At first this looked like a network config problem.

It was not.

It was a firewall problem on the Stora.

The subtle trap: static IP changed, firewall did not

I changed the Stora’s Debian network config from:

  • 192.168.0.165
  • gateway 192.168.0.1

to:

  • 192.168.50.10
  • gateway 192.168.50.1

That part was correct.

But the firewall rules on disk still allowed only:

  • 192.168.0.0/24 for SSH
  • 192.168.0.0/24 for ICMP

So from the Stora’s point of view, the new subnet was hostile.

The machine had moved, but the trust boundary had not.

That is why it looked alive from ARP but dead to ping and SSH.

Recovering the Stora from USB

Since the Stora boot Debian from USB and I could mount its installed root filesystem offline, the recovery path was straightforward:

  1. mount the installed root
  2. inspect /etc/network/interfaces
  3. inspect firewall files
  4. fix the allowed subnet
  5. unmount
  6. boot normally again

What I found was exactly the problem:

  • /etc/iptables/rules.v4 still trusted only 192.168.0.0/24
  • /etc/nftables.conf still trusted only 192.168.0.0/24

After updating those to 192.168.50.0/24, the Stora finally became reachable behind the MX64.

That was the last real blocker.

♪┏(・o・)┛♪

The final structure

At the end of all of this, the network looked the way I wanted:

  • main router on 192.168.0.0/24
  • MX64 WAN on that upstream network
  • MX64 LAN on 192.168.50.0/24
  • Arch host on the MX64 LAN
  • Stora on the MX64 LAN
  • Stora SSH/firewall allowing only the Meraki-side subnet

That is a much cleaner design than what I started with.

It gives me:

  • a separate controlled inner subnet
  • a dedicated OpenWrt firewall/routing boundary
  • storage available only behind that boundary
  • less accidental exposure
  • a better place to put weird old hardware that should not live directly on the main LAN

Backups and artifacts: what was worth keeping

One thing this process made obvious is that you do not want to lose the small pile of files that finally made everything work.

The useful things worth backing up were:

  • OpenWrt config backups
  • built initramfs image
  • built sysupgrade image
  • manifest
  • uboot_mx64
  • mtd
  • mtd-rw.ko
  • notes about addressing and pre-change state

The giant build tree was not worth preserving as a full backup. The outputs matter. The compiler sludge does not.

Lessons learned ᝰ.ᐟ

1. Documentation can be technically correct and still operationally confusing

The MX64 documentation is not pure fiction. It does contain the right ingredients. But when multiple generations of artifacts, repos, file names, and install flows coexist, it is very easy to end up “technically following” the process while still holding the wrong file in your hand.

2. If you cannot confidently identify the right binary, compile it yourself

This was the biggest breakthrough. Once I built the exact initramfs I needed, the ambiguity mostly disappeared.

3. Bootloader stage and install stage are different

This sounds obvious after the fact. It was not obvious while staring at similarly named files across multiple sources.

4. Serial problems create fake software problems

A noisy or flaky UART setup can make a healthy bootloader look broken.

5. Static IP changes are not enough if firewall policy still trusts only the old subnet

This bit me on the Stora. Hard. Hope I’ll never forget to check firewall rules before applying smth.

6. Layered routing is better than trying to make everything flat

Using the MX64 as an inner boundary behind the main router gave me exactly the security/control separation I wanted without having to redesign the entire network from scratch.

Was it worth it?

Yes.

Not because the process was fun.

It was worth it because now the MX64 is no longer a dead corporate brick. It is a useful part of the network:

  • running OpenWrt
  • routing cleanly
  • segmenting devices
  • hosting a more controlled internal subnet
  • acting like a proper piece of infrastructure instead of a proprietary liability

And the Stora, instead of being some dusty hardcoded Debian island, now lives behind that boundary where it belongs.

Final advice if you want to DYI

If you are about to do the same thing, here is the short version:

  • get serial working first
  • verify the exact hardware branch before flashing anything
  • replace U-Boot carefully
  • do not confuse old clayface boot artifacts with current OpenWrt install images
  • if the correct initramfs is unclear, compile it yourself
  • expect documentation mismatches
  • expect naming confusion
  • expect USB boot weirdness
  • expect firewall rules on old devices to betray you later
  • keep notes the entire time

And above all:

When you finally get it working, back everything up immediately, because you will not want to rediscover all of this from scratch six months later.
I really hope I will not have to… •𐃷•

← back to home