diff --git a/Documentation/Routers/ReadWriteSplit.md b/Documentation/Routers/ReadWriteSplit.md
index b18825f2a6..d834d9e186 100644
--- a/Documentation/Routers/ReadWriteSplit.md
+++ b/Documentation/Routers/ReadWriteSplit.md
@@ -1080,6 +1080,20 @@ Enabling this feature will increase memory usage of a session. The amount of
 memory stored per prepared statement is proportional to the length of the
 prepared SQL statement and the number of parameters the statement has.
 
+### `read_only`
+
+- **Type**: [boolean](../Getting-Started/Configuration-Guide.md#booleans)
+- **Mandatory**: No
+- **Dynamic**: Yes
+- **Default**: false
+
+If enabled, only read queries and read-only transactions are allowed. All write
+queries will generate the following read-only error:
+
+```
+ERROR 1290 (HY000): The MariaDB server is running with the --read-only option so it cannot execute this statement
+```
+
 ## Router Diagnostics
 
 The `router_diagnostics` output for a readwritesplit service contains the
diff --git a/server/modules/routing/readwritesplit/readwritesplit.cc b/server/modules/routing/readwritesplit/readwritesplit.cc
index 3355fb3da4..c971393883 100644
--- a/server/modules/routing/readwritesplit/readwritesplit.cc
+++ b/server/modules/routing/readwritesplit/readwritesplit.cc
@@ -232,6 +232,7 @@ RWSConfig::RWSConfig(SERVICE* service)
     add_native(&RWSConfig::m_v, &Values::optimistic_trx, &s_optimistic_trx);
     add_native(&RWSConfig::m_v, &Values::lazy_connect, &s_lazy_connect);
     add_native(&RWSConfig::m_v, &Values::reuse_ps, &s_reuse_ps);
+    add_native(&RWSConfig::m_v, &Values::read_only, &s_read_only);
 }
 
 bool RWSConfig::post_configure(const std::map<std::string, mxs::ConfigParameters>& nested_params)
diff --git a/server/modules/routing/readwritesplit/readwritesplit.hh b/server/modules/routing/readwritesplit/readwritesplit.hh
index 7a6da2eb28..8d3ede9495 100644
--- a/server/modules/routing/readwritesplit/readwritesplit.hh
+++ b/server/modules/routing/readwritesplit/readwritesplit.hh
@@ -239,6 +239,10 @@ static cfg::ParamBool s_reuse_ps(
     &s_spec, "reuse_prepared_statements", "Reuse identical prepared statements inside the same connection",
     false, cfg::Param::AT_RUNTIME);
 
+static cfg::ParamBool s_read_only(
+    &s_spec, "read_only", "Only allow read queries to be executed",
+    false, cfg::Param::AT_RUNTIME);
+
 /** Function that returns a "score" for a server to enable comparison.
  *  Smaller numbers are better.
  */
@@ -270,6 +274,7 @@ struct RWSConfig : public mxs::config::Configuration
         bool        master_reconnection;        /**< Allow changes in master server */
         bool        optimistic_trx;             /**< Enable optimistic transactions */
         bool        lazy_connect;               /**< Create connections only when needed */
+        bool        read_only;
         CausalReads causal_reads;
         seconds     causal_reads_timeout;   /**< Timeout, second parameter of function master_wait_gtid */
         bool        delayed_retry;          /**< Delay routing if no target found */
diff --git a/server/modules/routing/readwritesplit/rwsplit_route_stmt.cc b/server/modules/routing/readwritesplit/rwsplit_route_stmt.cc
index 2b86eab325..bcae229596 100644
--- a/server/modules/routing/readwritesplit/rwsplit_route_stmt.cc
+++ b/server/modules/routing/readwritesplit/rwsplit_route_stmt.cc
@@ -183,9 +183,7 @@ bool RWSplitSession::handle_routing_failure(GWBUF&& buffer, const RoutingPlan& r
     {
         MXB_INFO("Sending read-only error, no valid target found for %s",
                  route_target_to_string(res.route_target));
-        set_response(mariadb::create_error_packet(1, ER_OPTION_PREVENTS_STATEMENT, "HY000",
-                                                  "The MariaDB server is running with the --read-only"
-                                                  " option so it cannot execute this statement"));
+        queue_readonly_error();
         discard_connection(m_current_master, "The original primary is not available");
     }
     else if (res.route_target == TARGET_MASTER
@@ -220,6 +218,13 @@ void RWSplitSession::send_readonly_error()
     RouterSession::clientReply(mariadb::create_error_packet(1, errnum, sqlstate, errmsg), route, reply);
 }
 
+void RWSplitSession::queue_readonly_error()
+{
+    set_response(mariadb::create_error_packet(1, ER_OPTION_PREVENTS_STATEMENT, "HY000",
+                                              "The MariaDB server is running with the --read-only"
+                                              " option so it cannot execute this statement"));
+}
+
 bool RWSplitSession::query_not_supported(const GWBUF& querybuf)
 {
     bool unsupported = false;
diff --git a/server/modules/routing/readwritesplit/rwsplitsession.cc b/server/modules/routing/readwritesplit/rwsplitsession.cc
index 2c8afcd9da..d93a230d4c 100644
--- a/server/modules/routing/readwritesplit/rwsplitsession.cc
+++ b/server/modules/routing/readwritesplit/rwsplitsession.cc
@@ -112,7 +112,14 @@ bool RWSplitSession::route_query(GWBUF&& buffer)
 
     if (can_route_query(buffer, res, prev_trx_state))
     {
-        if (need_gtid_probe(res))
+        if (TARGET_IS_MASTER(res.route_target) && m_config->read_only)
+        {
+            // Revert any transaction state changes in case the statement changed it.
+            m_trx_tracker = prev_trx_state;
+            queue_readonly_error();
+            return true;
+        }
+        else if (need_gtid_probe(res))
         {
             m_query_queue.push_front(std::move(buffer));
             std::tie(buffer, res) = start_gtid_probe();
diff --git a/server/modules/routing/readwritesplit/rwsplitsession.hh b/server/modules/routing/readwritesplit/rwsplitsession.hh
index f50f0cfee8..d5550290e0 100644
--- a/server/modules/routing/readwritesplit/rwsplitsession.hh
+++ b/server/modules/routing/readwritesplit/rwsplitsession.hh
@@ -178,6 +178,7 @@ private:
                                     mxs::RWBackend* curr_master);
 
     void send_readonly_error();
+    void queue_readonly_error();
     bool query_not_supported(const GWBUF& querybuf);
 
     bool handle_causal_read_reply(GWBUF& writebuf, const mxs::Reply& reply, mxs::RWBackend* backend);
