diff --git a/gst-env.py b/gst-env.py
index 8525587cac..dbeb0aefe6 100755
--- a/gst-env.py
+++ b/gst-env.py
@@ -281,6 +281,9 @@ def get_subprocess_env(options, gst_version):
     prepend_env_var(env, "GST_VALIDATE_APPS_DIR", os.path.normpath(
         "%s/subprojects/gst-examples/webrtc/check/validate/apps" %
         SCRIPTDIR), options.sysroot)
+    env["GST_VALIDATE_LAUNCHER_HTTP_SERVER_PATH"] = os.path.normpath(
+        "%s/subprojects/gst-devtools/validate/launcher/RangeHTTPServer.py" %
+        SCRIPTDIR)
 
     if options.wine:
         return get_wine_subprocess_env(options, env)
diff --git a/subprojects/gst-devtools/validate/gst/validate/gst-validate-scenario.c b/subprojects/gst-devtools/validate/gst/validate/gst-validate-scenario.c
index 2309028713..5b2bfe2da7 100644
--- a/subprojects/gst-devtools/validate/gst/validate/gst-validate-scenario.c
+++ b/subprojects/gst-devtools/validate/gst/validate/gst-validate-scenario.c
@@ -44,6 +44,7 @@
 #include "config.h"
 #endif
 
+#include <stdio.h>
 #include <gst/gst.h>
 #include <gio/gio.h>
 #include <string.h>
@@ -187,6 +188,12 @@ typedef struct
   GstValidateAction *action;
 } GstValidateSeekInformation;
 
+typedef struct
+{
+  GSubprocess *subprocess;
+  gint port;
+} HTTPServer;
+
 /* GstValidateScenario is not really thread safe and
  * everything should be done from the thread GstValidate
  * was inited from, unless stated otherwise.
@@ -282,6 +289,8 @@ struct _GstValidateScenarioPrivate
   guint segments_needed;
 
   GMainContext *context;
+
+    GArray /*< HTTPServer> */  * http_servers;
 };
 
 typedef struct KeyFileGroupName
@@ -5735,6 +5744,32 @@ one_actions_scenario_max:
   }
 }
 
+static void
+gst_validate_scenario_stop_http_servers (GstValidateScenario * scenario)
+{
+  if (scenario->priv->http_servers) {
+    for (guint i = 0; i < scenario->priv->http_servers->len; i++) {
+      HTTPServer *server =
+          &g_array_index (scenario->priv->http_servers, HTTPServer, i);
+      if (server->subprocess) {
+        GError *error = NULL;
+        g_subprocess_force_exit (server->subprocess);
+
+        if (!g_subprocess_wait_check (server->subprocess, NULL, &error)) {
+          GST_WARNING_OBJECT (scenario,
+              "Error waiting for subprocess to exit: %s",
+              error ? error->message : "unknown error");
+          g_clear_error (&error);
+        }
+
+        g_clear_object (&server->subprocess);
+      }
+    }
+    g_array_free (scenario->priv->http_servers, TRUE);
+    scenario->priv->http_servers = NULL;
+  }
+}
+
 static void
 gst_validate_scenario_set_property (GObject * object, guint prop_id,
     const GValue * value, GParamSpec * pspec)
@@ -5936,6 +5971,8 @@ gst_validate_scenario_finalize (GObject * object)
     gst_structure_free (self->description);
   g_mutex_clear (&priv->lock);
 
+  gst_validate_scenario_stop_http_servers (self);
+
   G_OBJECT_CLASS (gst_validate_scenario_parent_class)->finalize (object);
 }
 
@@ -7105,10 +7142,10 @@ _execute_stop (GstValidateScenario * scenario, GstValidateAction * action)
   SCENARIO_UNLOCK (scenario);
 
   gst_validate_scenario_check_dropped (scenario);
-
   gst_bus_post (bus,
       gst_message_new_request_state (GST_OBJECT_CAST (scenario),
           GST_STATE_NULL));
+  gst_validate_scenario_stop_http_servers (scenario);
   gst_object_unref (bus);
   gst_object_unref (pipeline);
 
@@ -7471,6 +7508,86 @@ done:
   return res;
 }
 
+static GstValidateExecuteActionReturn
+_execute_start_http_server (GstValidateScenario * scenario,
+    GstValidateAction * action)
+{
+  GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK;
+  const gchar *server_path, *working_dir;
+  GError *err = NULL;
+  HTTPServer server = { 0 };
+  GInputStream *stdout_stream = NULL;
+  GSubprocess *subprocess = NULL;
+  gint port = 0;
+
+  server_path = g_getenv ("GST_VALIDATE_LAUNCHER_HTTP_SERVER_PATH");
+  REPORT_UNLESS (server_path, done,
+      "GST_VALIDATE_LAUNCHER_HTTP_SERVER_PATH not set");
+
+  REPORT_UNLESS (g_file_test (server_path,
+          G_FILE_TEST_IS_REGULAR), done,
+      "HTTP server script not found at: %s", server_path);
+
+  working_dir =
+      gst_structure_get_string (action->structure, "working-directory");
+  REPORT_UNLESS (working_dir, done, "working-directory not specified");
+  REPORT_UNLESS (g_file_test (working_dir,
+          G_FILE_TEST_IS_DIR), done,
+      "working-directory '%s' doesn't exist", working_dir);
+
+  gchar const *argv[3] = { server_path, "0", NULL };
+  gboolean no_pipe = FALSE;
+  gst_structure_get_boolean (action->structure, "no-pipe", &no_pipe);
+  GSubprocessLauncher *launcher =
+      g_subprocess_launcher_new (no_pipe ? G_SUBPROCESS_FLAGS_STDOUT_PIPE :
+      G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDERR_PIPE);
+  g_subprocess_launcher_set_cwd (launcher, working_dir);
+  subprocess =
+      g_subprocess_launcher_spawnv (launcher, (const gchar * const *) argv,
+      &err);
+  g_object_unref (launcher);
+
+  REPORT_UNLESS (subprocess, done,
+      "Failed to start HTTP server: %s", err->message);
+
+  stdout_stream = g_subprocess_get_stdout_pipe (subprocess);
+  GDataInputStream *data_stream = g_data_input_stream_new (stdout_stream);
+  gchar *line = g_data_input_stream_read_line (data_stream, NULL, NULL, &err);
+  g_object_unref (data_stream);
+
+  REPORT_UNLESS (err == NULL, done, "Failed to read server output: %s",
+      err->message);
+  REPORT_UNLESS (sscanf (line, "PORT: %d", &port) == 1, done,
+      "Failed to parse port number from server output: %s", line);
+
+  server.port = port;
+  server.subprocess = subprocess;
+
+  if (!scenario->priv->http_servers)
+    scenario->priv->http_servers =
+        g_array_new (FALSE, FALSE, sizeof (HTTPServer));
+
+  g_array_append_val (scenario->priv->http_servers, server);
+
+  gint i = 1;
+  gchar *port_varname = g_strdup ("http_server_port");
+  while (gst_structure_has_field (scenario->priv->vars, port_varname)) {
+    g_free (port_varname);
+    port_varname = g_strdup_printf ("http_server_port_%d", i++);
+  }
+
+  gst_structure_set (scenario->priv->vars, port_varname,
+      G_TYPE_INT, port, NULL);
+  g_free (port_varname);
+
+done:
+  if (subprocess && !server.subprocess) {
+    g_object_unref (subprocess);
+  }
+  g_clear_error (&err);
+
+  return res;
+}
 
 /**
  * gst_validate_action_get_scenario:
@@ -9018,6 +9135,40 @@ register_action_types (void)
       " using the `run-on-sub-pipeline` action type.",
       GST_VALIDATE_ACTION_TYPE_NONE);
 
+  REGISTER_ACTION_TYPE("start-http-server", _execute_start_http_server,
+      ((GstValidateActionParameter[]) {
+        {
+          .name = "working-directory",
+          .description = "Server’s current working directory",
+          .mandatory = TRUE,
+          .types = "string",
+          NULL
+        },
+        {
+          .name = "no-pipe",
+          .description = "Do not pipe http server stderr/stdout ",
+          .mandatory = FALSE,
+          .types = "bool",
+          .def = "false",
+          NULL,
+        },
+        {NULL}
+      }),
+      "Start an HTTP server in a separate process to serve files from $(working-directory).\n"
+      "The server is started on any available port and the action sets the `$(http_server_port)`\n"
+      "variable so it can be used afterward. For subsequent servers started, the\n"
+      "variable names become `http_server_port_1`, `http_server_port_2`, etc.\n\n"
+      "The server implementation must be specified through the GST_VALIDATE_LAUNCHER_HTTP_SERVER_PATH\n"
+      "environment variable. By default, it uses our `RangeHTTPServer.py` implementation which\n"
+      "provides support for HTTP range requests and directory listing as well as specific POST requests\n"
+      "for testing purposes (check the file for details).\n\n"
+      "Example:\n"
+      "```\n"
+      "- start-http-server, working-directory=/path/to/media/files\n"
+      "- set-property, playbin::uri=\"http://127.0.0.1:$(http_server_port)/video.mp4\"\n"
+      "```\n",
+      GST_VALIDATE_ACTION_TYPE_NONE);
+
     /* Internal actions types to test the validate scenario implementation */
     REGISTER_ACTION_TYPE("priv_check-action-type-calls",
       _execute_check_action_type_calls, NULL, NULL, 0);
diff --git a/subprojects/gst-devtools/validate/launcher/RangeHTTPServer.py b/subprojects/gst-devtools/validate/launcher/RangeHTTPServer.py
old mode 100644
new mode 100755
index aa0f048a2c..7c38b304cc
--- a/subprojects/gst-devtools/validate/launcher/RangeHTTPServer.py
+++ b/subprojects/gst-devtools/validate/launcher/RangeHTTPServer.py
@@ -50,8 +50,15 @@ import time
 _bandwidth = 0
 
 
+def debug(msg):
+    print(f'msg: {msg}', file=sys.stderr)
+
+
 class ThreadingSimpleServer(ThreadingMixIn, http.server.HTTPServer):
-    pass
+    def server_bind(self):
+        super().server_bind()
+        print(f"PORT: {self.server_port}")
+        sys.stdout.flush()
 
 
 class RangeHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
@@ -72,7 +79,7 @@ class RangeHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
     def do_GET(self):
         """Serve a GET request."""
         f, start_range, end_range = self.send_head()
-        print("Got values of {} and {}".format(start_range, end_range))
+        debug("Got values of {} and {}".format(start_range, end_range))
         if f:
             f.seek(start_range, 0)
             chunk = 0x1000
@@ -165,7 +172,7 @@ class RangeHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
         self.send_header("Content-Length", end_range - start_range)
         self.end_headers()
 
-        print("Sending bytes {} to {}...".format(start_range, end_range))
+        debug("Sending bytes {} to {}...".format(start_range, end_range))
         return (f, start_range, end_range)
 
     def list_directory(self, path):
@@ -289,3 +296,4 @@ def test(handler_class=RangeHTTPRequestHandler, server_class=http.server.HTTPSer
 if __name__ == "__main__":
     httpd = ThreadingSimpleServer(("0.0.0.0", int(sys.argv[1])), RangeHTTPRequestHandler)
     httpd.serve_forever()
+    print("EXIT")
diff --git a/subprojects/gst-devtools/validate/launcher/baseclasses.py b/subprojects/gst-devtools/validate/launcher/baseclasses.py
index 9686fcb83e..c157bcbbc0 100644
--- a/subprojects/gst-devtools/validate/launcher/baseclasses.py
+++ b/subprojects/gst-devtools/validate/launcher/baseclasses.py
@@ -2352,6 +2352,7 @@ class _TestsLauncher(Loggable):
             self._stop_server()
 
     def run_tests(self):
+        os.environ["GST_VALIDATE_LAUNCHER_HTTP_SERVER_PATH"] = os.path.join(os.path.dirname(__file__), "RangeHTTPServer.py")
         r = 0
         try:
             self._start_server()