Routing server on Debian 12

OSRM (Open Source Routing Machine) is a super-fast routing engine for OpenStreetMap (OSM) road networks. In previous tutorial, we explained the process of setting up a self-hosted OpenStreetMap tile server. This tutorial is going to show you how to add navigation functionality to your OpenStreetMap with OSRM.

1. Build OSRM from source

Install dependency packages.

sudo apt update
sudo apt install build-essential git cmake pkg-config doxygen libboost-all-dev libtbb-dev lua5.2 liblua5.2-dev libluabind-dev libstxxl-dev libstxxl1v5 libxml2 libxml2-dev libosmpbf-dev libbz2-dev libzip-dev libprotobuf-dev

Create the osrm user. (No need to create a password for this user.)

sudo useradd -d /srv/osrm -s /bin/bash -m osrm

Grant permissions to your own user account. Replace username with your real Linux username.

sudo apt install acl
sudo setfacl -R -m u:username:rwx /srv/osrm/

Change to the /srv/osrm/ directory.

cd /srv/osrm/

Download the OSRM source code from its Github repository.

git clone --branch v5.27.1 https://github.com/Project-OSRM/osrm-backend.git

You can also git clone to your local computer, zip it, and then upload to the server and unzip:

scp -i osrmOpen osrm-backend.zip yourusername@[2a01:4ff:f0:7b77::1]:/srv/osrm/
sudo apt install unzip
unzip osrm-backend.zip

Create the build directory.

mkdir build

To avoid the following error:

cc1plus: all warnings being treated as errors make[2]: *** [CMakeFiles/EXTRACTOR.dir/build.make:328: CMakeFiles/EXTRACTOR.dir/src/extractor/scripting_environment_lua.cpp.o] Error 1

You need to change CMakeLists.txt in /srv/osrm/osrm-backend folder. Comment this line (line 339):

# include(cmake/warnings.cmake)

In addition to this, I also had to comment lines 442 and 443:

# target_no_warning(MICROTAR unused-variable)
# target_no_warning(MICROTAR format)

Change to this directory and configure the build environment.

cd build
cmake /srv/osrm/osrm-backend/ -Wno-dev

Compile the source code. (If you see some TBB warning: deprecated message, you can ignore them.)

It takes long time to compile, so you can use screen to keep the process running.

make

[  0%] Building CXX object CMakeFiles/UTIL.dir/src/util/assert.cpp.o
[  0%] Building CXX object CMakeFiles/UTIL.dir/src/util/conditional_restrictions.cpp.o
[  0%] Building CXX object CMakeFiles/UTIL.dir/src/util/coordinate.cpp.o
[  0%] Building CXX object CMakeFiles/UTIL.dir/src/util/coordinate_calculation.cpp.o
[  0%] Building CXX object CMakeFiles/UTIL.dir/src/util/exception.cpp.o
[  4%] Building CXX object CMakeFiles/UTIL.dir/src/util/fingerprint.cpp.o
[  4%] Building CXX object CMakeFiles/UTIL.dir/src/util/geojson_debug_policies.cpp.o
[  4%] Building CXX object CMakeFiles/UTIL.dir/src/util/guidance/bearing_class.cpp.o
[  4%] Building CXX object CMakeFiles/UTIL.dir/src/util/guidance/entry_class.cpp.o
[  4%] Building CXX object CMakeFiles/UTIL.dir/src/util/guidance/turn_lanes.cpp.o
[  4%] Building CXX object CMakeFiles/UTIL.dir/src/util/log.cpp.o
[  8%] Building CXX object CMakeFiles/UTIL.dir/src/util/opening_hours.cpp.o
[  8%] Building CXX object CMakeFiles/UTIL.dir/src/util/timed_historgram.cpp.o
[  8%] Building CXX object CMakeFiles/UTIL.dir/src/util/timezones.cpp.o
In file included from /usr/include/boost/math/tools/cxx03_warn.hpp:9,
                 from /usr/include/boost/math/constants/constants.hpp:11,
                 from /usr/include/boost/geometry/util/math.hpp:29,
                 from /usr/include/boost/geometry/core/radian_access.hpp:33,
                 from /usr/include/boost/geometry/geometry.hpp:51,
                 from /usr/include/boost/geometry.hpp:17,
                 from /srv/osrm/osrm-backend/include/util/timezones.hpp:7,
                 from /srv/osrm/osrm-backend/src/util/timezones.cpp:1:
/usr/include/boost/function_output_iterator.hpp:14:1: note: ‘#pragma message: This header is deprecated. 
Use <boost/iterator/function_output_iterator.hpp> instead.’
   14 | BOOST_HEADER_DEPRECATED(“<boost/iterator/function_output_iterator.hpp>”
      | ^~~~~~~~~~~~~~~~~~~~~~~
13% Building CXX object CMakeFiles/EXTRACTOR.dir/src/extractor/extractor.cpp.o
In file included from /usr/include/boost/smart_ptr/detail/sp_thread_sleep.hpp:22,
                                                          yield_k.hpp:23,
                                                          spinlock_gcc_atomic.hpp:14,
                                                          spinlock.hpp:42
……

21% Building CXX object CMakeFiles/EXTRACTOR.dir/src/extractor/raster_source.cpp.o
21% Building CXX object CMakeFiles/EXTRACTOR.dir/src/extractor/restriction_graph.cpp.o
21% Building CXX object CMakeFiles/EXTRACTOR.dir/src/extractor/restriction_parser.cpp.o
21% Building CXX object CMakeFiles/EXTRACTOR.dir/src/extractor/scripting_environment_lua.cpp.o
In file included from /usr/include/boost/smart_ptr/detail/sp_thread_sleep.hpp:22,
                 from /usr/include/boost/smart_ptr/detail/yield_k.hpp:23,
                 from /usr/include/boost/smart_ptr/detail/spinlock_gcc_atomic.hpp:14,
                 from /usr/include/boost/smart_ptr/detail/spinlock.hpp:42,
                 from /usr/include/boost/smart_ptr/detail/spinlock_pool.hpp:25,
                 from /usr/include/boost/smart_ptr/shared_ptr.hpp:29,
                 from /usr/include/boost/shared_ptr.hpp:17,
                 from /usr/include/boost/format/alt_sstream.hpp:22,
                 from /usr/include/boost/format/internals.hpp:24,
                 from /usr/include/boost/format.hpp:38,
                 from /srv/osrm/osrm-backend/include/util/exception.hpp:38,
                 from /srv/osrm/osrm-backend/include/extractor/extraction_relation.hpp:4,
                from /srv/osrm/osrm-backend/include/extractor/scripting_environment_lua.hpp:4,
                from /srv/osrm/osrm-backend/src/extractor/scripting_environment_lua.cpp:1:
/usr/include/boost/detail/no_exceptions_support.hpp:17:1: note: ‘#pragma message: This header is deprecated. 
Use <boost/core/no_exceptions_support.hpp> instead.’
   17 | BOOST_HEADER_DEPRECATED(“<boost/core/no_exceptions_support.hpp>”)
      | ^~~~~~~~~~~~~~~~~~~~~~~
……  
        

Install the binaries.

sudo make install

The following binaries will be installed.

2. Generate OSRM routing data

Now we need to download the OpenStreetMap data and make it usable for routing. Run the following command to download the map data of the Maryland (195MB) in PBF (ProtoBufBinary) format.

wget -c https://download.geofabrik.de/north-america/us/maryland-latest.osm.pbf -P /srv/osrm/osrm-backend

If you want a map of an individual country/state/province/city, go to http://download.geofabrik.de.

Make sure you are in the /srv/osrm/osrm-backend/ directory.

cd /srv/osrm/osrm-backend/

Extract a graph out of the OpenStreetMap data.

osrm-extract maryland-latest.osm.pbf --threads=2

By default, it will use the car.lua profile.
        [2025-11-12T22:53:01.882265254] [info] Parsed 0 location-dependent features with 0 GeoJSON polygons
        [2025-11-12T22:53:01.882323182] [info] Using script profiles/car.lua
        [2025-11-12T22:53:01.882464286] [info] Input file: maryland-latest.osm.pbf
        [2025-11-12T22:53:01.882479645] [info] Profile: car.lua
        [2025-11-12T22:53:01.882483904] [info] Threads: 2
        [2025-11-12T22:53:01.882600652] [info] Parsing in progress..
        [2025-11-12T22:53:01.882825334] [info] input file generated by osmium/1.14.0
        [2025-11-12T22:53:01.882957983] [info] timestamp: 2025-11-11T21:21:30Z
        [2025-11-12T22:53:01.887276358] [info] Using profile api version 4
        [2025-11-12T22:53:01.887601177] [info] Found 3 turn restriction tags:
        [2025-11-12T22:53:01.887618260] [info]   motor_vehicle
        [2025-11-12T22:53:01.887625644] [info]   motorcar
        [2025-11-12T22:53:01.887630432] [info]   vehicle
        [2025-11-12T22:53:01.887681568] [info] Parse relations ...
        [2025-11-12T22:53:02.982318280] [info] Parse ways and nodes ...
        [2025-11-12T22:53:02.986341461] [info] Using profile api version 4

This process is resource-intensive. It uses 1.6GB RAM on my server and the CPU load goes up to 2. I use the Maryland map.

Once it’s finished, there will be files with the same filename but with the .osrm extension as well as other extensions. Run the following command to partition this graph recursively into cells.

osrm-partition maryland-latest.osrm

[2025-11-12T22:58:39.621128018] [info] Computing recursive bisection
[2025-11-12T22:58:39.717910677] [info] Loaded compressed node based graph: 2702344 edges, 4742031 nodes
[2025-11-12T22:58:39.777106776] [info]  running partition: 128 1.2 0.25 10 1000 # max_cell_size balance boundary cuts 
small_component_size
[2025-11-12T22:58:40.046057205] [info] Found 3629476 SCC (1 large, 3629475 small)
[2025-11-12T22:58:40.046097070] [info] SCC run took: 0.264543s
[2025-11-12T22:59:04.718396464] [info] Full bisection done in 24.2568s
[2025-11-12T22:59:04.743987817] [info] Loaded node based graph to edge based graph mapping
[2025-11-12T22:59:05.759426757] [info] Loaded edge based graph for mapping partition ids: 9654934 edges, 2582414 nodes
[2025-11-12T22:59:06.138803527] [info] Fixed 324 unconnected nodes
[2025-11-12T22:59:06.138843462] [info] Edge-based-graph annotation:
[2025-11-12T22:59:06.138847310] [info]   level 1 #cells 13114 bit size 14
[2025-11-12T22:59:06.139257279] [info]   level 2 #cells 952 bit size 10
[2025-11-12T22:59:06.139263691] [info]   level 3 #cells 62 bit size 6
[2025-11-12T22:59:06.139267058] [info]   level 4 #cells 3 bit size 2
[2025-11-12T22:59:08.820778825] [info] Renumbered data in 2.6815 seconds
[2025-11-12T22:59:08.982056204] [info] MultiLevelPartition constructed in 0.161123 seconds
[2025-11-12T22:59:09.153941236] [info] CellStorage constructed in 0.171819 seconds
[2025-11-12T22:59:09.354557698] [info] MLD data writing took 0.20055 seconds
[2025-11-12T22:59:09.354605156] [info] Cells statistics per level
[2025-11-12T22:59:09.364573010] [info] Level 1 #cells 13114 #boundary nodes 172268, sources: avg. 8, destinations: avg. 12, 
entries: 1930000 (15440000 bytes)
[2025-11-12T22:59:09.365717067] [info] Level 2 #cells 952 #boundary nodes 27201, sources: avg. 19, destinations: avg. 26, 
entries: 610424 (4883392 bytes)
[2025-11-12T22:59:09.365904549] [info] Level 3 #cells 62 #boundary nodes 4170, sources: avg. 45, destinations: avg. 59, 
entries: 210994 (1687952 bytes)
[2025-11-12T22:59:09.365918586] [info] Level 4 #cells 3 #boundary nodes 137, sources: avg. 30, destinations: avg. 35, 
entries: 4949 (39592 bytes)
[2025-11-12T22:59:09.368547702] [info] Bisection took 29.7473 seconds.
[2025-11-12T22:59:09.368564684] [info] RAM: peak bytes used: 702300160
        

Customize the cells by calculating routing weights for all cells.

osrm-customize maryland-latest.osrm

[2025-11-12T23:04:58.781401701] [info] Loaded edge based graph: 9654934 edges, 2582414 nodes
[2025-11-12T23:04:58.791836400] [info] Loading partition data took 1.30856 seconds
[2025-11-12T23:05:05.533197032] [info] Cells customization took 6.7413 seconds
[2025-11-12T23:05:05.533251512] [info] Cells statistics per level
[2025-11-12T23:05:05.540293802] [info] Level 1 #cells 13114 #boundary nodes 172268, sources: avg. 8, destinations: avg. 12, 
entries: 1930000 (15440000 bytes)
[2025-11-12T23:05:05.541446640] [info] Level 2 #cells 952 #boundary nodes 27201, sources: avg. 19, destinations: avg. 26, 
entries: 610424 (4883392 bytes)
[2025-11-12T23:05:05.541637581] [info] Level 3 #cells 62 #boundary nodes 4170, sources: avg. 45, destinations: avg. 59, 
entries: 210994 (1687952 bytes)
[2025-11-12T23:05:05.541657729] [info] Level 4 #cells 3 #boundary nodes 137, sources: avg. 30, destinations: avg. 35, 
entries: 4949 (39592 bytes)
[2025-11-12T23:05:05.541665612] [info] Unreachable nodes statistics per level
[2025-11-12T23:05:05.543776411] [warn] Level 1 unreachable boundary nodes per cell: 0.000915053 sources, 
0.000991307 destinations
[2025-11-12T23:05:05.544257676] [warn] Level 2 unreachable boundary nodes per cell: 0.00735294 sources, 
0 destinations
[2025-11-12T23:05:05.544410427] [warn] Level 3 unreachable boundary nodes per cell: 0.016129 sources, 
0 destinations
[2025-11-12T23:05:05.544431485] [warn] Level 4 unreachable boundary nodes per cell: 0.333333 sources, 
0 destinations
[2025-11-12T23:05:05.544443177] [info] Unreachable nodes statistics per level
[2025-11-12T23:05:05.546622921] [warn] Level 1 unreachable boundary nodes per cell: 0.00831173 sources, 0.00541406 destinations
[2025-11-12T23:05:05.547096652] [warn] Level 2 unreachable boundary nodes per cell: 0.0682773 sources, 0.0336134 destinations
[2025-11-12T23:05:05.547244193] [warn] Level 3 unreachable boundary nodes per cell: 0.516129 sources, 0.258065 destinations
[2025-11-12T23:05:05.547264450] [warn] Level 4 unreachable boundary nodes per cell: 0.666667 sources, 0.333333 destinations
[2025-11-12T23:05:05.547276041] [info] Unreachable nodes statistics per level
[2025-11-12T23:05:05.549399302] [warn] Level 1 unreachable boundary nodes per cell: 0.122693 sources, 0.0758731 destinations
[2025-11-12T23:05:05.549933655] [warn] Level 2 unreachable boundary nodes per cell: 0.951681 sources, 0.536765 destinations
[2025-11-12T23:05:05.550101904] [warn] Level 3 unreachable boundary nodes per cell: 4.5 sources, 2.67742 destinations
[2025-11-12T23:05:05.550122572] [warn] Level 4 unreachable boundary nodes per cell: 5.66667 sources, 3.33333 destinations
[2025-11-12T23:05:05.550133623] [info] Unreachable nodes statistics per level
[2025-11-12T23:05:05.552152973] [warn] Level 1 unreachable boundary nodes per cell: 0.00106756 sources, 0.00114382 destinations
[2025-11-12T23:05:05.552614811] [warn] Level 2 unreachable boundary nodes per cell: 0.00840336 sources, 0.00105042 destinations
[2025-11-12T23:05:05.552736184] [warn] Level 3 unreachable boundary nodes per cell: 0.016129 sources, 0 destinations
[2025-11-12T23:05:05.552755129] [warn] Level 4 unreachable boundary nodes per cell: 0.333333 sources, 0 destinations
[2025-11-12T23:05:05.637747306] [info] MLD customization writing took 0.084966 seconds
[2025-11-12T23:05:05.781055707] [info] Graph writing took 0.14326 seconds
[2025-11-12T23:05:05.783944224] [info] RAM: peak bytes used: 658309120
        

Now you can start the routing engine.

osrm-routed --algorithm=MLD maryland-latest.osrm

xtao@tile:/srv/osrm/osrm-backend$ osrm-routed --algorithm=MLD maryland-latest.osrm
[2025-11-12T23:09:46.324878731] [info] starting up engines, v5.27.1
[2025-11-12T23:09:46.325791452] [info] Threads: 2
[2025-11-12T23:09:46.325800418] [info] IP address: 0.0.0.0
[2025-11-12T23:09:46.325803835] [info] IP port: 5000
[2025-11-12T23:09:46.614771989] [info] http 1.1 compression handled by zlib version 1.2.13
[2025-11-12T23:09:46.614973181] [info] Listening on: 0.0.0.0:5000
[2025-11-12T23:09:46.615263620] [info] running and waiting for requests
        

As you can see, it listens on TCP port 5000.

3. Creating a systemd service

We can manually run the OSRM routing engine with osrm-routed --algorithm=MLD maryland-latest.osrm, but it’s more convenient to run osrm-routed as a systemd service in the background.

Press Ctrl+C to stop the current osrm-routed process and create a systemd service unit file for osrm-routed with the following command.

sudo vi /etc/systemd/system/osrm-routed.service

Put the following lines into the file.

[Unit]
Description=Open Source Routing Machine
Wants=network-online.target
After=network.target network-online.target

[Service]
ExecStart=/usr/local/bin/osrm-routed --algorithm=MLD /srv/osrm/osrm-backend/maryland-latest.osrm
User=osrm
Group=osrm
Restart=always
RestartSec=5s

[Install]
WantedBy=multi-user.target

Save and close the file. Change the ownership of the /srv/osrm/osrm-backend/ directory.

sudo chown osrm:osrm /srv/osrm/osrm-backend/ -R

Now we can start and enable the osrm-routed systemd service.

sudo systemctl start osrm-routed
sudo systemctl enable osrm-routed

Check status.

systemctl status osrm-routed

xtao@tile:/srv/osrm/osrm-backend$ systemctl status osrm-routed
● osrm-routed.service - Open Source Routing Machine
     Loaded: loaded (/etc/systemd/system/osrm-routed.service; enabled; preset: enabled)
     Active: active (running) since Wed 2025-11-12 23:20:15 UTC; 24s ago
   Main PID: 6489 (osrm-routed)
      Tasks: 4 (limit: 2250)
     Memory: 529.5M
        CPU: 184ms
     CGroup: /system.slice/osrm-routed.service
             └─6489 /usr/local/bin/osrm-routed --algorithm=MLD /srv/osrm/osrm-backend/maryland-latest.osrm
        

4. Set up reverse proxy

We can configure Apache web server as a reverse proxy for the osrm-routed service, so we will be able to use a domain name to access the routing service and also enable HTTPS encryption. Install Apache web server.

sudo apt install apache2

To use Apache as a reverse proxy, we need to enable the proxy, proxy_http and rewrite module.

sudo a2enmod proxy proxy_http rewrite

Then create a virtual host file for OSRM.

sudo vi /etc/apache2/sites-available/osrm.conf

Add the following texts into the file. Replace osrm.your-domain.com with your actual domain name and don’t forget to create DNS A record for it.

<VirtualHost *:80>
ServerName osrm.your-domain.com
ProxyPass / http://127.0.0.1:5000/
ProxyPassReverse / http://127.0.0.1:5000/
</VirtualHost>

Save and close the file. Then enable this virtual host.

sudo a2ensite osrm.conf

Reload Apache for the changes to take effect.

sudo systemctl reload apache2

Now you can remotely access OSRM by entering the domain name (osrm.your-domain.com) in browser address bar.

5. Enable HTTPS

We can enable HTTPS by installing a free TLS certificate issued from Let’s Encrypt. In the Tile server set up, we have already installed the Let’s Encrypt client (certbot). So we just need to run the following command to obtain and install TLS certificate.

sudo certbot --apache --agree-tos --email your-account@example.com -d osrm.your-domain.com

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Requesting a certificate for osrm.umd.me.uk

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/osrm.umd.me.uk/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/osrm.umd.me.uk/privkey.pem
This certificate expires on 2026-02-10.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.

Deploying certificate
Successfully deployed certificate for osrm.umd.me.uk to /etc/apache2/sites-available/osrm-le-ssl.conf
Congratulations! You have successfully enabled HTTPS on https://osrm.umd.me.uk
        

6. Integrate OSRM with a slippy map

I assume your slippy map is displayed using the Leaflet JavaScript library.

To integrate OSRM with a slippy map, we can use a plugin called Leaflet Routing Machine. First, include the Leaflet routing machine JavaScript and CSS file to your slippy map. Note that they should be placed after the main Leaflet JavaScript and the Leaflet Control Geocoder JavaScript.

Download leaflet-routing-machine.css, leaflet-routing-machine.min.js, and leaflet.routing.icons.png and routing-icon.png into dist folder:

sudo mkdir /var/www/html/dist
cd /var/www/html/dist
sudo wget https://unpkg.com/leaflet-routing-machine@latest/dist/leaflet-routing-machine.css
sudo wget https://unpkg.com/leaflet-routing-machine@latest/dist/leaflet-routing-machine.js
sudo wget https://unpkg.com/leaflet-routing-machine@latest/dist/leaflet.routing.icons.png
sudo wget https://unpkg.com/leaflet-routing-machine@latest/dist/routing-icon.png

        <html>
          <head>
             ....
             ....
             <link rel="stylesheet" href="dist/leaflet-routing-machine.css" />
             <script src="dist/leaflet-routing-machine.js"></script>
         </head>
         <body>
         ....
         ....
         </body>
        </html>
        
        

Next, add the following lines to the <script>...</script> snippet in the HTML body.

    end = [38.985978, -76.942564]; // University of Maryland
    start = [38.99883, -76.90663]; // Target
    L.Routing.control({
        serviceUrl: 'https://osrm.your-domain.com/route/v1',
        waypoints: [
            L.latLng(start[0], start[1]),
            L.latLng(end[0], end[1])
        ],
        routeWhileDragging: true
    }).addTo(map);

        
Like this:
        <html>
          <head>
             ....
             ....
             <link rel="stylesheet" href="dist/leaflet-routing-machine.css" />
             <script src="dist/leaflet-routing-machine.js"></script>
         </head>
         <body>
           <div id="map"></div>
             <script>
             ....
             ....
             end = [38.985978, -76.942564]; // University of Maryland
             start = [38.99883, -76.90663]; // Target
             L.Routing.control({
                serviceUrl: 'https://osrm.your-domain.com/route/v1',
                waypoints: [
                    L.latLng(start[0], start[1]),
                    L.latLng(end[0], end[1])
                ],
                routeWhileDragging: true
             }).addTo(map);
             </script>

         </body>
        </html>
        

Save and close the file. Then reload the map in your web browser, you should see a control panel on the upper-right corner, where you can interact with the steps.

You can drag the waypoints on the map and OSRM will automatically recalculate the route.