Skip to content

Instantly share code, notes, and snippets.

@jirrick
Last active February 2, 2026 05:05
Show Gist options
  • Select an option

  • Save jirrick/b7985532edf5ed92feffacb1f7345b40 to your computer and use it in GitHub Desktop.

Select an option

Save jirrick/b7985532edf5ed92feffacb1f7345b40 to your computer and use it in GitHub Desktop.
Hackintosh multiboot

Running Multiple macOS Versions on a Hackintosh: A Multiboot Setup with OpenCore and rEFInd

The Situation

So here's the deal—I've got this Z97-based Hackintosh with an i7-4770 and an RX 580, and it's basically a time machine for macOS versions. Both CPU and GPU are natively supported from Sierra (10.12) through Monterey(12), and with the right SMBIOS configuration and some essential kexts, these versions run almost like they would on actual Apple hardware. Newer releases like Ventura and Sonoma? Also doable, but you're looking at heavier kext dependencies and OCLP patches—essentially entering "it works but the maintenance overhead is real" territory.

The how-to-get-each-version-running part isn't what this post is about though. This is about what happens after you've figured out how to boot each version of macOS on it's own (from an USB stick for example) and realized that having them coexist peacefully on one drive is its own separate nightmare.

The Core Problem (Or: Why OpenCore's Flexibility Has Limits)

OpenCore is genuinely impressive at what it does. It's flexible, well-documented (by Hackintosh standards, which is a low bar, but still), and handles the heavy lifting of making non-Apple hardware pretend to be a Mac. The thing is—and this is where my setup started falling apart—OpenCore operates on a "one config to rule them all" philosophy.

You get one config.plist. One set of SMBIOS settings, kexts, ACPI patches, and boot arguments.

This works fine if you're running a single macOS version. But I've got a 1TB SSD that can easily hold multiple 60-100 GB macOS partitions (I'm sticking to APFS-compatible versions, so High Sierra and newer), and each version has different requirements:

  • Different SMBIOS: Mojave might want iMac15,1, while Monterey prefers iMac17,1 for optimal compatibility. Use the wrong one and you're looking at anything from subtle bugs to straight-up boot failures.
  • Different kext configurations: Some kexts are version-specific, some need different settings per OS version.
  • Different boot arguments: What works for one version might cause kernel panics on another.

The result? You can install multiple macOS versions and OpenCore will happily show all of them in the boot picker—including the ones that absolutely will not boot with your current config. It's like a restaurant menu where half the items will give you food poisoning, but there's no indication which ones. Classic.

I briefly considered the "just swap config.plist files manually" approach, but that's basically asking for human error to ruin my day. There had to be a better way.

The Solution: Two Bootloaders, One Filter Mechanism

After going down various rabbit holes (some productive, most not), I landed on a setup that actually works. It's not exactly elegant in the "single unified solution" sense, but it's robust and—most importantly—it doesn't require me to remember which config goes with which OS.

The architecture breaks down into two main layers with what I'd call a "half-layer" for volume filtering:

Layer 1: rEFInd — The initial boot manager that the BIOS hands off to. It presents a clean menu where I pick which OpenCore instance to load.

Layer 2: Multiple OpenCore Instances — Each macOS version gets its own complete OpenCore setup with version-appropriate settings. They live in separate subdirectories under \EFI\OC\.

The Half-Layer: .contentVisibility Files — These sit in each macOS installation's Preboot volume and tell specific OpenCore instances to ignore that volume. It's not really a "layer" in the boot chain sense—more like a filter that runs during OpenCore's volume discovery. But as a special sauce that makes everything work together it deserves its place in the spotlights.

I also bumped my EFI partition to 512MB to fit multiple complete OpenCore installations. The default 200MB works fine for a single config, but once you're duplicating the entire OC folder structure multiple times, you'll hit space constraints fast.

The Actual Boot Flow

Here's what happens when I power on:

  1. BIOS → rEFInd: The firmware loads \EFI\BOOT\BOOTx64.efi, which is rEFInd. I see a clean menu with entries like "macOS Mojave (OC)", "macOS Monterey (OC)", "Windows", etc.

  2. rEFInd → OpenCore: I select an entry, and rEFInd chainloads the corresponding OpenCore.efi from that version's subdirectory.

  3. OpenCore → macOS: OpenCore does its thing—applies patches, injects kexts, and presents its own boot picker. But here's the key part: thanks to the .contentVisibility filtering, it only shows the appropriate macOS volume. No more "which one of these five options won't kernel panic" guessing games.

  4. The Auxiliary Mode Bonus: Pressing Space in OpenCore's picker reveals auxiliary entries—Recovery, Reset NVRAM, etc. Currently, this also shows Recovery partitions from other macOS installations (haven't filtered those out yet, but it should work the same way). Honestly, repairing borked installation is a moment when the brain shouldn't be on autopilot anyway so having to pick the right recovery environment is a brain warm up - not a bug but a feature!

The whole sequence is about as seamless as running two bootloaders in series can be. There's a brief rEFInd screen, then a brief OpenCore screen, then macOS. Total added boot time is maybe 3-4 seconds of menu navigation, which is an acceptable tax for the flexibility.


Setting Up rEFInd as the Primary Boot Manager

rEFInd goes into \EFI\BOOT\ so the firmware picks it up as the default bootloader. The critical configuration bit in refind.conf:

scanfor external,optical,manual

This disables auto-scanning for internal bootloaders—otherwise rEFInd would discover all the OpenCore instances and the macOS installations directly, creating a cluttered mess of duplicate entries. We want manual control here.

Defining the Menu Entries

menuentry "macOS Mojave (OC)" {
    icon \EFI\BOOT\icons\os_mac.png
    loader \EFI\OC\Mojave\OpenCore.efi
}

menuentry "macOS Monterey (OC)" {
    icon \EFI\BOOT\icons\os_mac.png
    loader \EFI\OC\Monterey\OpenCore.efi
}

menuentry "Windows 10" {
    icon \EFI\BOOT\icons\os_win8.png
    volume WINEFI
    loader \EFI\Microsoft\Boot\bootmgfw.efi
}

menuentry "Parted Magic" {
    icon \EFI\BOOT\icons\tool_rescue.png
    volume PARTEDMAGIC
    loader \EFI\boot\bootx64.efi
}

Straightforward stuff. Each OpenCore instance gets its own entry pointing to its specific OpenCore.efi.


Configuring the OpenCore Instances

Each instance lives in its own directory with a complete, independent setup. All examples are with two macOS versions but can be scaled up:

EFI/
├── BOOT/
│   ├── BOOTx64.efi        ← rEFInd (as a default bootloader)
│   ├── refind.conf
│   └── ...
└── OC/
    ├── Mojave/
    │   ├── OpenCore.efi
    │   ├── config.plist   ← Mojave-specific SMBIOS & settings
    │   ├── ACPI/
    │   ├── Drivers/
    │   └── Kexts/
    └── Monterey/
        ├── OpenCore.efi
        ├── config.plist   ← Monterey-specific SMBIOS & settings
        ├── ACPI/
        ├── Drivers/
        └── Kexts/

The Settings That Matter for This Setup

Most of your config.plist will be version-specific (SMBIOS, kexts, patches, etc.), but these settings are specifically required for the multi-instance isolation to work:

Misc → Boot:

<key>HideAuxiliary</key>
<true/>
<key>PickerMode</key>
<string>External</string>
<key>ShowPicker</key>
<true/>
<key>InstanceIdentifier</key>
<string>OC-MOJAVE</string>

The InstanceIdentifier is the key piece—it's what the .contentVisibility files use to target specific OpenCore instances. Make it unique and descriptive.

Misc → Security:

<key>ScanPolicy</key>
<integer>257</integer>

ScanPolicy = 257 limits scanning to internal APFS drives. Keeps the picker clean and speeds up boot slightly.

Instance InstanceIdentifier
Mojave OC-MOJAVE
Monterey OC-MONTEREY

The .contentVisibility Filter (The Actually Clever Part)

This is where the magic happens—and honestly, it took me way too long to discover this feature existed. The .contentVisibility file is an OpenCore mechanism that lets you hide specific volumes from specific OpenCore instances.

How It Works

Every macOS installation has a Preboot volume with a UUID-named directory:

/System/Volumes/Preboot/<UUID>/System/Library/CoreServices/

OpenCore scans these Preboot directories to discover bootable macOS installations. Drop a .contentVisibility file in there, and you can tell specific OpenCore instances to skip that volume entirely.

The Syntax

Dead simple—one line, no whitespace, no trailing newline:

<InstanceIdentifier>:Disabled

In case more instances should be filtered it's a comma separated list (again no whitespaces):

<InstanceIdentifier1>,<InstanceIdentifier2>:Disabled

Practical Example

Say you've got:

  • Mojave — Preboot UUID: 5174715E-A61F-444E-B527-E27CC7BBCE9C
  • Monterey — Preboot UUID: CB2D58B1-D1BD-41B3-B0C3-FBE3AC031AE7

To hide Monterey from the Mojave OpenCore instance:

cd /Volumes/Preboot/CB2D58B1-D1BD-41B3-B0C3-FBE3AC031AE7/System/Library/CoreServices
echo "OC-MOJAVE:Disabled" > .contentVisibility

To hide Mojave from the Monterey OpenCore instance:

cd /Volumes/Preboot/5174715E-A61F-444E-B527-E27CC7BBCE9C/System/Library/CoreServices
echo "OC-MONTEREY:Disabled" > .contentVisibility

Now when I boot via Mojave's OpenCore, only Mojave shows up. Boot via Monterey's OpenCore, only Monterey. No cross-contamination, no "oops wrong volume" moments.

Creating These Files

The Preboot volume isn't mounted by default in a running macOS, so you'll need to do this from Recovery:

  1. Boot into macOS Recovery from OpenCore
  2. Open Terminal from Utilities menu
  3. Mount the Preboot volume:
    diskutil apfs list
    diskutil mount diskXsY  # Your Preboot identifier
    ls /Volumes/Preboot
  4. Navigate to the CoreServices folder for given volume and create the files as shown above
  5. Unmount the volume diskutil umount diskXsY and repeat the same process for every macOS installation
  6. Reboot and verify each OpenCore instance only shows its intended volume

Adding More macOS Versions

The setup scales pretty well. To add another version:

  1. Create \EFI\OC\<NewVersion> with a complete OpenCore configuration tuned for that OS version
  2. Set a unique InstanceIdentifier (e.g., OC-SONOMA)
  3. Install macOS to a new partition
  4. Update .contentVisibility files:
    • Add the new identifier to existing installations' Preboot directories
    • Create .contentVisibility in the new installation's Preboot, listing all other instance identifiers
  5. Add a rEFInd menu entry

The .contentVisibility management does get a bit tedious as you add more versions—each new installation means updating N existing files and creating one new file with N-1 entries. (Foreshadowing for a future "I should script this" post, probably.)


Summary

Component Purpose
rEFInd at \EFI\BOOT\ First-stage boot manager, presents OC instance selection
Multiple OC directories Version-specific configs, SMBIOS, kexts
InstanceIdentifier Unique ID for .contentVisibility targeting
ScanPolicy = 257 Limit to internal APFS drives
.contentVisibility Hide volumes from non-matching OC instances

The Outcome

So after all this—BIOS hands off to rEFInd, I pick an OpenCore instance, that instance shows only its designated macOS volume (with Recovery and other auxiliary stuff available via Space key), and I boot into a properly configured system. It's about as seamless as chaining two bootloaders can realistically be.

The setup preserves software update compatibility, doesn't require manual config swapping, and scales to additional macOS versions without architectural changes. Is it more complex than a single-boot Hackintosh? Obviously. But for a testing environment where I need multiple macOS versions with different configurations, it's the most maintainable solution I've found.

Still need to filter out the cross-instance Recovery entries from auxiliary mode, but that's a problem for future me. Same goes for the .contentVisibility file management. The core functionality works, and that's what matters.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment