diff --git a/include/maxscale/listener.hh b/include/maxscale/listener.hh
index 184d0e42cc..b399ab23b2 100644
--- a/include/maxscale/listener.hh
+++ b/include/maxscale/listener.hh
@@ -68,7 +68,8 @@ public:
     ListenerData(SSLContext ssl, mxs::Parser::SqlMode default_sql_mode, SERVICE* service,
                  SProtocol protocol_module, const std::string& listener_name,
                  std::vector<SAuthenticator>&& authenticators, ConnectionInitSql&& init_sql,
-                 SMappingInfo mapping, mxb::proxy_protocol::SubnetArray&& proxy_networks);
+                 SMappingInfo mapping, mxb::proxy_protocol::SubnetArray&& proxy_networks,
+                 const std::string& redirect_url);
 
     ListenerData(const ListenerData&) = delete;
     ListenerData& operator=(const ListenerData&) = delete;
@@ -78,6 +79,7 @@ public:
     SERVICE&                   m_service;               /**< The service the listener feeds */
     const SProtocol            m_proto_module;          /**< Protocol module */
     const std::string          m_listener_name;         /**< Name of the owning listener */
+    const std::string          m_redirect_url;
 
     /**
      * Authenticator modules used by the sessions created from the listener. The session will select
@@ -127,6 +129,7 @@ public:
         std::string          connection_init_sql_file;
         std::string          user_mapping_file;
         std::string          proxy_networks;
+        std::string          redirect_url;
 
         // TLS configuration parameters
         bool        ssl;
diff --git a/include/maxscale/protocol/mariadb/client_connection.hh b/include/maxscale/protocol/mariadb/client_connection.hh
index 1fd57ab0d8..cd27368672 100644
--- a/include/maxscale/protocol/mariadb/client_connection.hh
+++ b/include/maxscale/protocol/mariadb/client_connection.hh
@@ -197,6 +197,8 @@ private:
     GWBUF* create_standard_error(int sequence, int error_number, const char* msg);
     void   write_ok_packet(int sequence, uint8_t affected_rows = 0);
 
+    std::map<std::string, std::string> get_sysvar_values();
+
     /**
      * Send an error packet to the client.
      *
diff --git a/include/maxscale/protocol/mariadb/mysql.hh b/include/maxscale/protocol/mariadb/mysql.hh
index 5ff1fb9cf7..42794c633d 100644
--- a/include/maxscale/protocol/mariadb/mysql.hh
+++ b/include/maxscale/protocol/mariadb/mysql.hh
@@ -16,6 +16,7 @@
 #include <maxscale/ccdefs.hh>
 
 #include <cstring>
+#include <map>
 
 #include <maxscale/buffer.hh>
 #include <maxscale/protocol/mariadb/common_constants.hh>
@@ -203,7 +204,8 @@ bool is_com_prepare(const GWBUF& buf);
  */
 bool is_com_query_or_prepare(const GWBUF& buf);
 
-GWBUF create_ok_packet(uint8_t sequence, uint64_t affected_rows);
+GWBUF create_ok_packet(uint8_t sequence, uint64_t affected_rows,
+                       const std::map<std::string, std::string>& variables = {});
 
 GWBUF create_query(std::string_view query);
 
diff --git a/server/core/listener.cc b/server/core/listener.cc
index 9bd805e143..6f524ea0f6 100644
--- a/server/core/listener.cc
+++ b/server/core/listener.cc
@@ -143,6 +143,9 @@ cfg::ParamString s_proxy_networks(
     &s_spec, CN_PROXY_PROTOCOL_NETWORKS, "Allowed (sub)networks for proxy protocol connections. Should be "
                                          "a comma-separated list of IPv4 or IPv6 addresses.", "", RUNTIME);
 
+cfg::ParamString s_redirect_url(
+    &s_spec, "redirect_url", "Redirection URL sent to all connecting clients.", "", RUNTIME);
+
 template<class Params>
 bool ListenerSpecification::do_post_validate(Params& params) const
 {
@@ -255,12 +258,14 @@ ListenerData::ListenerData(SSLContext ssl, mxs::Parser::SqlMode default_sql_mode
                            const std::string& listener_name,
                            std::vector<SAuthenticator>&& authenticators,
                            ListenerData::ConnectionInitSql&& init_sql, SMappingInfo mapping,
-                           mxb::proxy_protocol::SubnetArray&& proxy_networks)
+                           mxb::proxy_protocol::SubnetArray&& proxy_networks,
+                           const std::string& redirect_url)
     : m_ssl(move(ssl))
     , m_default_sql_mode(default_sql_mode)
     , m_service(*service)
     , m_proto_module(move(protocol_module))
     , m_listener_name(listener_name)
+    , m_redirect_url(redirect_url)
     , m_authenticators(move(authenticators))
     , m_conn_init_sql(std::move(init_sql))
     , m_mapping_info(move(mapping))
@@ -481,6 +486,7 @@ Listener::Config::Config(const std::string& name, Listener* listener)
     add_native(&Listener::Config::connection_init_sql_file, &s_connection_init_sql_file);
     add_native(&Listener::Config::user_mapping_file, &s_user_mapping_file);
     add_native(&Listener::Config::proxy_networks, &s_proxy_networks);
+    add_native(&Listener::Config::redirect_url, &s_redirect_url);
 }
 
 bool Listener::Config::post_configure(const std::map<std::string, mxs::ConfigParameters>& nested_params)
@@ -1379,7 +1385,7 @@ Listener::SData Listener::create_shared_data(const mxs::ConfigParameters& protoc
                 rval = std::make_shared<mxs::ListenerData>(
                     move(ssl), m_config.sql_mode, m_config.service, move(protocol_module),
                     m_name, move(authenticators), move(init_sql), move(mapping_info),
-                    std::move(proxy_networks));
+                    std::move(proxy_networks), m_config.redirect_url);
             }
         }
     }
diff --git a/server/modules/protocol/MariaDB/mariadb_client.cc b/server/modules/protocol/MariaDB/mariadb_client.cc
index 99542ca634..eafa190386 100644
--- a/server/modules/protocol/MariaDB/mariadb_client.cc
+++ b/server/modules/protocol/MariaDB/mariadb_client.cc
@@ -2857,9 +2857,30 @@ bool MariaDBClientConnection::process_normal_packet(GWBUF&& buffer)
     return success;
 }
 
+std::map<std::string, std::string> MariaDBClientConnection::get_sysvar_values()
+{
+    std::map<std::string, std::string> rval;
+    rval.emplace("threads_connected", std::to_string(m_session->service->stats().n_current_conns()));
+
+    if (const auto& url = m_session->listener_data()->m_redirect_url; !url.empty())
+    {
+        rval.emplace("redirect_url", url);
+    }
+
+    return rval;
+}
+
+
 void MariaDBClientConnection::write_ok_packet(int sequence, uint8_t affected_rows)
 {
-    write(mariadb::create_ok_packet(sequence, affected_rows));
+    if (m_session_data->client_caps.basic_capabilities & GW_MYSQL_CAPABILITIES_CONNECT_ATTRS)
+    {
+        write(mariadb::create_ok_packet(sequence, affected_rows, get_sysvar_values()));
+    }
+    else
+    {
+        write(mariadb::create_ok_packet(sequence, affected_rows));
+    }
 }
 
 bool MariaDBClientConnection::send_mysql_err_packet(int mysql_errno, const char* sqlstate_msg,
diff --git a/server/modules/protocol/MariaDB/mariadb_common.cc b/server/modules/protocol/MariaDB/mariadb_common.cc
index af5bcf4e29..46232d96be 100644
--- a/server/modules/protocol/MariaDB/mariadb_common.cc
+++ b/server/modules/protocol/MariaDB/mariadb_common.cc
@@ -26,6 +26,8 @@
 #include <maxscale/utils.hh>
 #include "packet_parser.hh"
 
+#include <mysql.h>
+
 using std::string;
 using std::move;
 using mxs::ReplyState;
@@ -712,7 +714,58 @@ AuthSwitchReqContents parse_auth_switch_request(const GWBUF& input)
     return packet_parser::parse_auth_switch_request(data);
 }
 
-GWBUF create_ok_packet(uint8_t sequence, uint64_t affected_rows)
+uint32_t total_sysvar_bytes(const std::map<std::string, std::string>& attrs)
+{
+    size_t total = 0;
+
+    for (const auto& [key, value] : attrs)
+    {
+        // Each system variable is encoded as:
+        // int<1>         Change type (0)
+        // int<lenenc>    Total length
+        // string<lenenc> Variable name
+        // string<lenenc> Variable value
+        size_t payload_bytes = leint_prefix_bytes(key.size()) + key.size()
+            + leint_prefix_bytes(value.size()) + value.size();
+        size_t payload_prefix_bytes = leint_prefix_bytes(payload_bytes);
+        total += 1 + payload_prefix_bytes + payload_bytes;
+    }
+
+    return total;
+}
+
+uint8_t* encode_sysvar(uint8_t* ptr, std::string_view key, std::string_view value)
+{
+    size_t key_prefix_bytes = leint_prefix_bytes(key.size());
+    size_t value_prefix_bytes = leint_prefix_bytes(value.size());
+    size_t payload_bytes = key_prefix_bytes + key.size() + value_prefix_bytes + value.size();
+    size_t payload_prefix_bytes = leint_prefix_bytes(payload_bytes);
+
+    // The state change type, SESSION_TRACK_SYSTEM_VARIABLES
+    // See: https://mariadb.com/kb/en/ok_packet/#session-state-info
+    *ptr++ = 0;
+
+    // The total length of the payload
+    encode_leint(ptr, payload_prefix_bytes, payload_bytes);
+    ptr += payload_prefix_bytes;
+
+    // The variable name
+    encode_leint(ptr, key_prefix_bytes, key.size());
+    ptr += key_prefix_bytes;
+    memcpy(ptr, key.data(), key.size());
+    ptr += key.size();
+
+    // The variable value
+    encode_leint(ptr, value_prefix_bytes, value.size());
+    ptr += value_prefix_bytes;
+    memcpy(ptr, value.data(), value.size());
+    ptr += value.size();
+
+    return ptr;
+}
+
+GWBUF create_ok_packet(uint8_t sequence, uint64_t affected_rows,
+                       const std::map<std::string, std::string>& attrs)
 {
     /* Basic ok packet is
      * 4 bytes header
@@ -721,10 +774,26 @@ GWBUF create_ok_packet(uint8_t sequence, uint64_t affected_rows)
      * 1 byte insert id = 0
      * 2 bytes server status
      * 2 bytes warning counter
-     * Total 4 + 6 + affected_rows size (1 to 9 bytes)
+     * 1 byte info (empty length-encoded string)
+     * Total 4 + 7 + affected_rows size (1 to 9 bytes)
+     *
+     * If the session state change attributes are not empty, they take up some extra space.
      */
     auto affected_rows_size = leint_prefix_bytes(affected_rows);
-    const uint32_t pl_size = 6 + affected_rows_size;
+    uint32_t attr_size = 0;
+    uint32_t attr_prefix_size = 0;
+    uint32_t attr_total_size = 0;
+    uint16_t server_status = SERVER_STATUS_AUTOCOMMIT;
+
+    if (!attrs.empty())
+    {
+        attr_size = total_sysvar_bytes(attrs);
+        attr_prefix_size = leint_prefix_bytes(attr_size);
+        attr_total_size = attr_size + attr_prefix_size;
+        server_status |= SERVER_SESSION_STATE_CHANGED;
+    }
+
+    const uint32_t pl_size = 6 + affected_rows_size + 1 + attr_total_size;
     const uint32_t total_size = MYSQL_HEADER_LEN + pl_size;
     GWBUF buffer(total_size);
     auto ptr = mariadb::write_header(buffer.data(), pl_size, sequence);
@@ -732,8 +801,21 @@ GWBUF create_ok_packet(uint8_t sequence, uint64_t affected_rows)
     encode_leint(ptr, affected_rows_size, affected_rows);
     ptr += affected_rows_size;
     *ptr++ = 0;
-    ptr += mariadb::set_byte2(ptr, 2);      // autocommit is on
-    ptr += mariadb::set_byte2(ptr, 0);      // no warnings
+    ptr += mariadb::set_byte2(ptr, server_status);  // Server status
+    ptr += mariadb::set_byte2(ptr, 0);              // No warnings
+    *ptr++ = 0;                                     // No info
+
+    if (attr_total_size)
+    {
+        encode_leint(ptr, attr_prefix_size, attr_size);
+        ptr += attr_prefix_size;
+
+        for (const auto& [key, value] : attrs)
+        {
+            ptr = encode_sysvar(ptr, key, value);
+        }
+    }
+
     mxb_assert(ptr - buffer.data() == total_size);
     return buffer;
 }
