Turning a Meraki MX64 into OpenWrt, against its will
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:
- get OpenWrt image
- TFTP or USB boot it
- sysupgrade
- done
That is not what this box wants.
The MX64 install path is more like:
- get serial access
- get into the right recovery/diagnostic environment
- inspect the exact hardware state
- determine if boot flash is locked
- determine if it is A0 or not
- replace stock U-Boot with a custom one
- boot the right temporary image
- 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?shbusybox shash
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
bcm5862xartifacts - newer
bcm53xxartifacts - 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
mtdutility - 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:
|
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.binopenwrt-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.binopenwrt-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/24for SSH192.168.0.0/24for 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:
- mount the installed root
- inspect
/etc/network/interfaces - inspect firewall files
- fix the allowed subnet
- unmount
- boot normally again
What I found was exactly the problem:
/etc/iptables/rules.v4still trusted only192.168.0.0/24/etc/nftables.confstill trusted only192.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_mx64mtdmtd-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… •𐃷•