diff --git a/swh/web/api/views/origin.py b/swh/web/api/views/origin.py
--- a/swh/web/api/views/origin.py
+++ b/swh/web/api/views/origin.py
@@ -446,7 +446,7 @@
 
 
 @api_route(
-    r"/origin/(?P<origin_url>.+)" "/intrinsic-metadata", "api-origin-intrinsic-metadata"
+    r"/origin/(?P<origin_url>.+)/intrinsic-metadata/", "api-origin-intrinsic-metadata"
 )
 @api_doc("/origin/intrinsic-metadata/")
 @format_docstring()
diff --git a/swh/web/auth/mailmap.py b/swh/web/auth/mailmap.py
--- a/swh/web/auth/mailmap.py
+++ b/swh/web/auth/mailmap.py
@@ -109,10 +109,10 @@
 
 
 urlpatterns = [
-    url(r"^profile/mailmap/list$", profile_list_mailmap, name="profile-mailmap-list",),
-    url(r"^profile/mailmap/add$", profile_add_mailmap, name="profile-mailmap-add",),
+    url(r"^profile/mailmap/list/$", profile_list_mailmap, name="profile-mailmap-list",),
+    url(r"^profile/mailmap/add/$", profile_add_mailmap, name="profile-mailmap-add",),
     url(
-        r"^profile/mailmap/update$",
+        r"^profile/mailmap/update/$",
         profile_update_mailmap,
         name="profile-mailmap-update",
     ),
diff --git a/swh/web/browse/views/content.py b/swh/web/browse/views/content.py
--- a/swh/web/browse/views/content.py
+++ b/swh/web/browse/views/content.py
@@ -67,7 +67,7 @@
 
 
 @browse_route(
-    r"content/(?P<from_query_string>.*)/diff/(?P<to_query_string>.*)",
+    r"content/(?P<from_query_string>.*)/diff/(?P<to_query_string>.*)/",
     view_name="diff-contents",
 )
 def _contents_diff(request, from_query_string, to_query_string):
diff --git a/swh/web/misc/fundraising.py b/swh/web/misc/fundraising.py
--- a/swh/web/misc/fundraising.py
+++ b/swh/web/misc/fundraising.py
@@ -58,5 +58,5 @@
 
 
 urlpatterns = [
-    url(r"^fundraising/banner", fundraising_banner, name="swh-fundraising-banner"),
+    url(r"^fundraising/banner/$", fundraising_banner, name="swh-fundraising-banner"),
 ]
diff --git a/swh/web/misc/iframe.py b/swh/web/misc/iframe.py
--- a/swh/web/misc/iframe.py
+++ b/swh/web/misc/iframe.py
@@ -330,7 +330,7 @@
 
 urlpatterns = [
     url(
-        r"^embed/(?P<swhid>swh:[0-9]+:[a-z]+:[0-9a-f]+.*)$",
+        r"^embed/(?P<swhid>swh:[0-9]+:[a-z]+:[0-9a-f]+.*)/$",
         swhid_iframe,
         name="swhid-iframe",
     ),
diff --git a/swh/web/misc/origin_save.py b/swh/web/misc/origin_save.py
--- a/swh/web/misc/origin_save.py
+++ b/swh/web/misc/origin_save.py
@@ -92,7 +92,7 @@
         name="origin-save-requests-list",
     ),
     url(
-        r"^save/task/info/(?P<save_request_id>.+)/",
+        r"^save/task/info/(?P<save_request_id>.+)/$",
         _save_origin_task_info,
         name="origin-save-task-info",
     ),
diff --git a/swh/web/misc/urls.py b/swh/web/misc/urls.py
--- a/swh/web/misc/urls.py
+++ b/swh/web/misc/urls.py
@@ -48,7 +48,7 @@
     url(r"^", include("swh.web.misc.coverage")),
     url(r"^jslicenses/$", _jslicenses, name="jslicenses"),
     url(r"^", include("swh.web.misc.origin_save")),
-    url(r"^stat_counters/", _stat_counters, name="stat-counters"),
+    url(r"^stat_counters/$", _stat_counters, name="stat-counters"),
     url(r"^", include("swh.web.misc.badges")),
     url(r"^metrics/prometheus/$", prometheus_metrics, name="metrics-prometheus"),
     url(r"^", include("swh.web.misc.iframe")),
diff --git a/swh/web/tests/api/test_throttling.py b/swh/web/tests/api/test_throttling.py
--- a/swh/web/tests/api/test_throttling.py
+++ b/swh/web/tests/api/test_throttling.py
@@ -70,10 +70,10 @@
 
 
 urlpatterns += [
-    url(r"^scope1_class$", MockViewScope1.as_view()),
-    url(r"^scope2_func$", mock_view_scope2),
-    url(r"^scope3_class$", MockViewScope3.as_view()),
-    url(r"^scope3_func$", mock_view_scope3),
+    url(r"^scope1_class/$", MockViewScope1.as_view()),
+    url(r"^scope2_func/$", mock_view_scope2),
+    url(r"^scope3_class/$", MockViewScope3.as_view()),
+    url(r"^scope3_func/$", mock_view_scope3),
 ]
 
 
@@ -95,19 +95,19 @@
     Ensure request rate is limited in scope1
     """
     for i in range(scope1_limiter_rate):
-        response = api_client.get("/scope1_class")
+        response = api_client.get("/scope1_class/")
         check_response(response, 200, scope1_limiter_rate, scope1_limiter_rate - i - 1)
 
-    response = api_client.get("/scope1_class")
+    response = api_client.get("/scope1_class/")
     check_response(response, 429, scope1_limiter_rate, 0)
 
     for i in range(scope1_limiter_rate_post):
-        response = api_client.post("/scope1_class")
+        response = api_client.post("/scope1_class/")
         check_response(
             response, 200, scope1_limiter_rate_post, scope1_limiter_rate_post - i - 1
         )
 
-    response = api_client.post("/scope1_class")
+    response = api_client.post("/scope1_class/")
     check_response(response, 429, scope1_limiter_rate_post, 0)
 
 
@@ -117,19 +117,19 @@
     Ensure request rate is limited in scope2
     """
     for i in range(scope2_limiter_rate):
-        response = api_client.get("/scope2_func")
+        response = api_client.get("/scope2_func/")
         check_response(response, 200, scope2_limiter_rate, scope2_limiter_rate - i - 1)
 
-    response = api_client.get("/scope2_func")
+    response = api_client.get("/scope2_func/")
     check_response(response, 429, scope2_limiter_rate, 0)
 
     for i in range(scope2_limiter_rate_post):
-        response = api_client.post("/scope2_func")
+        response = api_client.post("/scope2_func/")
         check_response(
             response, 200, scope2_limiter_rate_post, scope2_limiter_rate_post - i - 1
         )
 
-    response = api_client.post("/scope2_func")
+    response = api_client.post("/scope2_func/")
     check_response(response, 429, scope2_limiter_rate_post, 0)
 
 
@@ -140,19 +140,19 @@
     requests coming from localhost are exempted from rate limit.
     """
     for _ in range(scope3_limiter_rate + 1):
-        response = api_client.get("/scope3_class")
+        response = api_client.get("/scope3_class/")
         check_response(response, 200)
 
     for _ in range(scope3_limiter_rate_post + 1):
-        response = api_client.post("/scope3_class")
+        response = api_client.post("/scope3_class/")
         check_response(response, 200)
 
     for _ in range(scope3_limiter_rate + 1):
-        response = api_client.get("/scope3_func")
+        response = api_client.get("/scope3_func/")
         check_response(response, 200)
 
     for _ in range(scope3_limiter_rate_post + 1):
-        response = api_client.post("/scope3_func")
+        response = api_client.post("/scope3_func/")
         check_response(response, 200)
 
 
@@ -163,11 +163,11 @@
     api_client.force_login(staff_user)
 
     for _ in range(scope2_limiter_rate + 1):
-        response = api_client.get("/scope2_func")
+        response = api_client.get("/scope2_func/")
         check_response(response, 200)
 
     for _ in range(scope2_limiter_rate_post + 1):
-        response = api_client.post("/scope2_func")
+        response = api_client.post("/scope2_func/")
         check_response(response, 200)
 
 
@@ -182,12 +182,12 @@
     )
 
     for i in range(scope2_limiter_rate_user):
-        response = api_client.get("/scope2_func")
+        response = api_client.get("/scope2_func/")
         check_response(
             response, 200, scope2_limiter_rate_user, scope2_limiter_rate_user - i - 1
         )
 
-    response = api_client.get("/scope2_func")
+    response = api_client.get("/scope2_func/")
     check_response(response, 429, scope2_limiter_rate_user, 0)
 
     scope2_limiter_rate_post_user = (
@@ -195,7 +195,7 @@
     )
 
     for i in range(scope2_limiter_rate_post_user):
-        response = api_client.post("/scope2_func")
+        response = api_client.post("/scope2_func/")
         check_response(
             response,
             200,
@@ -203,7 +203,7 @@
             scope2_limiter_rate_post_user - i - 1,
         )
 
-    response = api_client.post("/scope2_func")
+    response = api_client.post("/scope2_func/")
     check_response(response, 429, scope2_limiter_rate_post_user, 0)
 
 
@@ -222,9 +222,9 @@
     api_client.force_login(regular_user)
 
     for _ in range(scope2_limiter_rate + 1):
-        response = api_client.get("/scope2_func")
+        response = api_client.get("/scope2_func/")
         check_response(response, 200)
 
     for _ in range(scope2_limiter_rate_post + 1):
-        response = api_client.post("/scope2_func")
+        response = api_client.post("/scope2_func/")
         check_response(response, 200)
diff --git a/swh/web/tests/test_urls.py b/swh/web/tests/test_urls.py
new file mode 100644
--- /dev/null
+++ b/swh/web/tests/test_urls.py
@@ -0,0 +1,13 @@
+# Copyright (C) 2022  The Software Heritage developers
+# See the AUTHORS file at the top-level directory of this distribution
+# License: GNU Affero General Public License version 3, or any later version
+# See top-level LICENSE file for more information
+
+from django.urls import get_resolver
+
+
+def test_swh_web_urls_have_trailing_slash():
+    urls = set(value[1] for value in get_resolver().reverse_dict.values())
+    for url in urls:
+        if url != "$":
+            assert url.endswith("/$")
diff --git a/swh/web/urls.py b/swh/web/urls.py
--- a/swh/web/urls.py
+++ b/swh/web/urls.py
@@ -43,7 +43,7 @@
 
 urlpatterns = [
     url(r"^admin/", include("swh.web.admin.urls")),
-    url(r"^favicon\.ico$", favicon_view),
+    url(r"^favicon\.ico/$", favicon_view),
     url(r"^api/", include("swh.web.api.urls")),
     url(r"^browse/", include("swh.web.browse.urls")),
     url(r"^$", _default_view, name="swh-web-homepage"),
@@ -73,7 +73,7 @@
 
 # enable to serve compressed assets through django development server
 if swh_web_config["serve_assets"]:
-    static_pattern = r"^%s(?P<path>.*)$" % settings.STATIC_URL[1:]
+    static_pattern = r"^%s(?P<path>.*)/$" % settings.STATIC_URL[1:]
     urlpatterns.append(url(static_pattern, insecure_serve))