diff --git a/roles/backbone_babel/defaults/main.yml b/roles/backbone_babel/defaults/main.yml
index c7e00daf93f5fd8d8eb3034b5a5ee4dd10e8bcbd..26b5cc7e0083748a8b881c9e6abd779989714289 100644
--- a/roles/backbone_babel/defaults/main.yml
+++ b/roles/backbone_babel/defaults/main.yml
@@ -3,3 +3,4 @@ bb_babel_wg_private_key_path: /etc/wireguard/wg-private.key
 bb_babel_wg_public_key_path: /etc/wireguard/wg-public.key
 bb_babel_loopback_ipv6: []
 bb_babel_loopback_ipv4: []
+bb_babel_routing_daemon: babeld
diff --git a/roles/backbone_babel/tasks/main.yml b/roles/backbone_babel/tasks/main.yml
index ae4258ae80c4b02b503a2002d5241efe9878d598..6c126f97df25c785c751c1abb4bb5b7936148365 100644
--- a/roles/backbone_babel/tasks/main.yml
+++ b/roles/backbone_babel/tasks/main.yml
@@ -59,3 +59,9 @@
 - name: Set up babel routing daemon
   ansible.builtin.include_tasks:
     file: routing_babeld.yml
+  when: "bb_babel_routing_daemon == 'babeld'"
+
+- name: Set up bird routing daemon
+  ansible.builtin.include_tasks:
+    file: routing_bird.yml
+  when: "bb_babel_routing_daemon == 'bird'"
diff --git a/roles/backbone_babel/tasks/routing_bird.yml b/roles/backbone_babel/tasks/routing_bird.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d72bd338ae0d33868b4062bea909630d11cf028e
--- /dev/null
+++ b/roles/backbone_babel/tasks/routing_bird.yml
@@ -0,0 +1,34 @@
+---
+- name: Install bird2
+  ansible.builtin.apt:
+    name: bird2
+    state: present
+
+- name: Mask babeld to avoid conflicts with bird
+  ansible.builtin.systemd_service:
+    name: babeld
+    enabled: false
+    masked: yes
+    state: stopped
+
+- name: Enable bird
+  ansible.builtin.systemd_service:
+    name: bird
+    enabled: true
+    masked: no
+    state: started
+
+- name: Ensure bird config is included
+  ansible.builtin.lineinfile:
+    line: 'include "/etc/bird/bird-backbone-babel.conf";'
+    regexp: '^include "/etc/bird/bird-backbone-babel.conf"'
+    path: /etc/bird/bird.conf
+    create: true
+
+- name: "Deploy bird config"
+  ansible.builtin.template:
+    src: bird-backbone-babel.conf.j2
+    dest: "/etc/bird/bird-backbone-babel.conf"
+  vars:
+    # list remote line names for connections we are on either side (line_a or line_b) of
+    interfaces: "{{ bb_babel_wg_connections|selectattr('line_a', 'in', local_lines)|map(attribute='line_b') + bb_babel_wg_connections|selectattr('line_b', 'in', local_lines)|map(attribute='line_a') + bb_babel_peer_interfaces|default([]) }}"
diff --git a/roles/backbone_babel/templates/bird-backbone-babel.conf.j2 b/roles/backbone_babel/templates/bird-backbone-babel.conf.j2
new file mode 100644
index 0000000000000000000000000000000000000000..bca852e5262243400d89b4d8008fea41ae268d3e
--- /dev/null
+++ b/roles/backbone_babel/templates/bird-backbone-babel.conf.j2
@@ -0,0 +1,50 @@
+# ANSIBLE managed
+filter import_backbone_babel {
+{% for network in bb_babel_networks|ansible.utils.ipv6 %}
+	if net.type = NET_IP6 && net ~ [{{ network }}+] then {
+		accept;
+	}
+{% endfor %}
+{% for network in bb_babel_networks|ansible.utils.ipv4 %}
+	if net.type = NET_IP4 && net ~ [{{ network }}+] then {
+		accept;
+	}
+{% endfor %}
+	reject;
+};
+filter export_backbone_babel {
+	if ! (source ~ [RTS_STATIC, RTS_BABEL]) then {
+		reject;
+	}
+{% for network in bb_babel_networks|ansible.utils.ipv6 %}
+	if net.type = NET_IP6 && net ~ [{{ network }}+] then {
+		accept;
+	}
+{% endfor %}
+{% for network in bb_babel_networks|ansible.utils.ipv4 %}
+	if net.type = NET_IP4 && net ~ [{{ network }}+] then {
+		accept;
+	}
+{% endfor %}
+	reject;
+};
+
+protocol babel backbone_babel {
+{% for interface in interfaces %}
+        interface "{{ interface }}" {
+                type wired;
+        };
+{% endfor %}
+        ipv6 {
+                import filter import_backbone_babel;
+		import keep filtered on;
+                export filter export_backbone_babel;
+        };
+
+        ipv4 {
+                import filter import_backbone_babel;
+		import keep filtered on;
+                export filter export_backbone_babel;
+        };
+}
+