Linux handles virtual MIDI better than either of its competitors and almost nobody says so, because the good news is boring: there is nothing to install. The ALSA sequencer has been in the kernel for decades, every MIDI-aware application publishes its own ports to it automatically, and routing between them is a one-line shell command. What trips people up is that the Linux model is different in kind — you do not "create a port and pick it from a dropdown", you connect two clients that already exist. This guide covers that model properly, plus snd-virmidi, PipeWire, udev permissions, and making routing persist.
- Nothing to install. The ALSA sequencer is in the kernel; apps publish their own ports.
aconnect -llists every client and port.aconnect 128:0 129:0connects them.snd-virmidiis only needed when software demands a raw MIDI device node.aseqdumpis the fastest sanity check in MIDI on any platform.- Connections do not survive a reboot — script them in a user systemd unit.
How do I create a virtual MIDI port on Linux?
In most cases you do not create one at all — the ALSA sequencer is built into the kernel and every ALSA client publishes its own ports automatically, which you then wire together with aconnect.
This is the conceptual jump from Windows. On Windows you install loopMIDI, create a port called MyBus, and then two apps independently select MyBus from their dropdowns. On Linux, launching an app is creating the port. Your DAW has ports. Your synth has ports. Universal Controller MIDI has ports. The job is connecting them.
# Is the sequencer present? (Rare to be missing, but check first.)
lsmod | grep snd_seq
sudo modprobe snd_seq # only if the above returned nothing
# Every sequencer client, with its numeric id and ports
aconnect -l
# Inputs only / outputs only
aconnect -i
aconnect -o
Output looks like client 128: 'Universal Controller' [type=user] followed by indented port lines. The number before the colon is the client; the number after is the port. That client:port pair is the address you route with.
How do I route one ALSA MIDI port to another?
Use aconnect <source> <destination> with client:port addresses, and aconnect -d with the same arguments to break the connection again.
# Route client 128 port 0 into client 129 port 0
aconnect 128:0 129:0
# Names work too, and are far less brittle than numbers
aconnect "Universal Controller:0" "FLUID Synth:0"
# Remove one connection
aconnect -d 128:0 129:0
# Remove every connection in the system (useful reset before a gig)
aconnect -x Use names rather than numbers wherever you can. Client ids are allocated in start order, so the number that worked yesterday points at something else today if you launched your apps in a different sequence. Scripts built on numeric ids are a reliable source of confusing failures.
What does snd-virmidi do, and do I need it?
The snd-virmidi kernel module exposes Virtual Raw MIDI devices with persistent /dev/snd device nodes, which you only need when software insists on a raw MIDI device rather than a sequencer client.
# Expose two virtual raw MIDI devices
sudo modprobe snd-virmidi midi_devs=2
# They now appear as sequencer clients AND device nodes
aconnect -l | grep -i virmidi
ls /dev/snd/midiC*D*
# Load at boot
echo "snd-virmidi" | sudo tee /etc/modules-load.d/snd-virmidi.conf
echo "options snd-virmidi midi_devs=2" | sudo tee /etc/modprobe.d/snd-virmidi.conf
Most modern Linux audio software speaks the sequencer API and does not need this. Older tools, some trackers, and the occasional cross-platform app that was written against a raw MIDI device do. If your app shows an empty MIDI device list while aconnect -l clearly shows ports, snd-virmidi is usually the missing piece.
How do I check MIDI is actually flowing?
Run aseqdump -p "<port name>" and operate the control — every note, CC and clock message prints as it arrives, which tells you instantly whether the problem is upstream or downstream.
# Live dump of a port
aseqdump -p "Universal Controller"
# Send a test file into a port
aplaymidi -p "Universal Controller:0" test.mid
# Confirm what PipeWire sees of the same client
pw-cli list-objects | grep -i universal aseqdump is the single most useful MIDI debugging tool on any operating system, and Linux is the only platform that ships it by default. If events print here but your DAW is silent, stop blaming the port — the issue is channel filtering, an unarmed track, or input not enabled in the host.
Does PipeWire replace ALSA MIDI?
No — PipeWire bridges ALSA sequencer ports into its own graph so JACK-style applications see them, but the underlying ALSA ports remain exactly where they were.
In practice this means both toolsets work at once: aconnect still routes, and Bitwig, Reaper and Ardour see the same clients through the PipeWire/JACK side without extra configuration. If you are running a PREEMPT-patched kernel or a tuned audio setup, treat the MIDI graph as independent of your audio buffer settings — they are separate concerns, and MIDI timing issues on Linux are far more often scheduling or buffer problems than port problems. The latency tuning docs break the chain down stage by stage.
Linux virtual MIDI tooling compared
Nearly everything you need is already installed; the table below is mostly about knowing which name to reach for.
| Tool | What it does | Ships with | Use it when |
|---|---|---|---|
aconnect | Lists and connects sequencer ports | alsa-utils | Any routing task, scriptable |
aseqdump | Prints incoming MIDI events live | alsa-utils | Proving a port is carrying data |
aplaymidi | Plays a MIDI file into a port | alsa-utils | Testing a destination without hardware |
snd-virmidi | Creates raw MIDI device nodes | Kernel module | An app wants a device node, not a client |
qpwgraph / Carla | Visual patchbay | Install separately | You prefer dragging cables |
| loopMIDI | Virtual port creator | Windows only | Never — no Linux build exists |
Why does my controller show up as root-only?
The hidraw device defaults to restricted permissions, so without a udev rule granting user access the bridge cannot open the controller at all.
The .deb package installs the rule for you. AppImage and tarball users have to drop it in by hand — this is the single most common Linux setup failure, and it looks like a MIDI problem when it is actually a permissions problem:
sudo tee /etc/udev/rules.d/70-universal-controller.rules <<'EOF'
# Sony DualSense (PS5)
KERNEL=="hidraw*", ATTRS{idVendor}=="054c", ATTRS{idProduct}=="0ce6", MODE="0660", TAG+="uaccess"
# Sony DualShock 4 (PS4) — both revisions
KERNEL=="hidraw*", ATTRS{idVendor}=="054c", ATTRS{idProduct}=="05c4", MODE="0660", TAG+="uaccess"
KERNEL=="hidraw*", ATTRS{idVendor}=="054c", ATTRS{idProduct}=="09cc", MODE="0660", TAG+="uaccess"
# Xbox Wireless Controller (Bluetooth)
KERNEL=="hidraw*", ATTRS{idVendor}=="045e", MODE="0660", TAG+="uaccess"
# Nintendo Switch Pro
KERNEL=="hidraw*", ATTRS{idVendor}=="057e", ATTRS{idProduct}=="2009", MODE="0660", TAG+="uaccess"
EOF
sudo udevadm control --reload-rules && sudo udevadm trigger Unplug and replug the controller afterwards — the rule applies on device connection, not retroactively. Full install matrix per distro: Linux install docs.
How do I make routing persist across reboots?
Put the aconnect commands in a user systemd unit or your desktop's autostart, because sequencer connections are runtime state that the kernel does not save.
# ~/.config/systemd/user/midi-routing.service
[Unit]
Description=Restore ALSA MIDI routing
After=sound.target
[Service]
Type=oneshot
ExecStart=/bin/sh -c 'sleep 5; aconnect "Universal Controller:0" "Ardour:0"'
RemainAfterExit=yes
[Install]
WantedBy=default.target systemctl --user daemon-reload
systemctl --user enable --now midi-routing.service
The sleep is not elegant but it is honest: the applications have to be running before their ports exist. If you want something more robust, watch for the client with aconnect -l in a short polling loop rather than guessing a fixed delay.
Frequently asked questions
How do I create a virtual MIDI port on Linux?
In most cases you do not create one at all — the ALSA sequencer is built into the kernel and every ALSA client publishes its own ports automatically, which you then wire together with aconnect.
What does snd-virmidi do?
The snd-virmidi kernel module exposes Virtual Raw MIDI devices with persistent /dev/snd device nodes, which is useful for software that insists on a raw MIDI device rather than a sequencer client.
What is the Linux equivalent of loopMIDI?
The ALSA sequencer plus aconnect — loopMIDI is Windows-only software, and Linux has equivalent functionality built into the kernel with no install required.
Why does my ALSA MIDI connection disappear after a reboot?
aconnect connections are runtime state held by the sequencer, so you need an autostart script or a user systemd unit to recreate them at login.
Does PipeWire replace ALSA MIDI?
No — PipeWire bridges ALSA sequencer ports into its own graph so JACK-style applications such as Bitwig, Reaper and Ardour see them, but the underlying ALSA ports remain.
Why does my controller show up as root-only on Linux?
The hidraw device defaults to restricted permissions, so without a udev rule granting user access the bridge cannot open the controller — install the rule and replug the device.
Putting it to work
Once you internalise aconnect -l → aconnect A B → aseqdump, Linux MIDI routing stops being mysterious and starts being the most scriptable setup of the three platforms. Everything is addressable, everything is inspectable, and nothing requires a driver download.
If the thing you want on the other end of that connection is a game controller, download Universal Controller MIDI — it ships as an AppImage, .deb and tarball, registers as a normal ALSA sequencer client, and runs headless with --no-ui for Raspberry Pi and rack setups. Next: Ardour on Linux with a gamepad, or the cross-platform virtual port guide.