diff --git a/doc/maintenance/index.md b/doc/maintenance/index.md
index da34c9ee0d5f75b319bf81b7590aafecea237222_ZG9jL21haW50ZW5hbmNlL2luZGV4Lm1k..6acfae1d0666073f35bc22a32acef95ae4fb2514_ZG9jL21haW50ZW5hbmNlL2luZGV4Lm1k 100644
--- a/doc/maintenance/index.md
+++ b/doc/maintenance/index.md
@@ -181,7 +181,7 @@
 1. Recommended. Extract your current secrets for `gitlab_rails`. Save the output because you need this later:
 
    ```shell
-   sudo grep "secret_key_base\|db_key_base\|otp_key_base\|encrypted_settings_key_base\|openid_connect_signing_key" /etc/gitlab/gitlab-secrets.json
+   sudo grep "secret_key_base\|db_key_base\|otp_key_base\|encrypted_settings_key_base\|openid_connect_signing_key\|active_record_encryption_primary_key\|active_record_encryption_deterministic_key\|active_record_encryption_key_derivation_salt" /etc/gitlab/gitlab-secrets.json
    ```
 
 1. Move your current secrets file to a different location:
diff --git a/doc/update/convert_to_omnibus.md b/doc/update/convert_to_omnibus.md
index da34c9ee0d5f75b319bf81b7590aafecea237222_ZG9jL3VwZGF0ZS9jb252ZXJ0X3RvX29tbmlidXMubWQ=..6acfae1d0666073f35bc22a32acef95ae4fb2514_ZG9jL3VwZGF0ZS9jb252ZXJ0X3RvX29tbmlidXMubWQ= 100644
--- a/doc/update/convert_to_omnibus.md
+++ b/doc/update/convert_to_omnibus.md
@@ -47,7 +47,8 @@
    `gitlab.yml`, `puma.rb`, and `smtp_settings.rb`. For all available options, see the
    [`gitlab.rb` template](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-config-template/gitlab.rb.template).
 1. Copy the secrets from the old self-compiled installation to the new Linux package installation:
-   1. Restore secrets related to Rails. Copy the values of `db_key_base`, `secret_key_base` and `otp_key_base` from
+   1. Restore secrets related to Rails. Copy the values of `db_key_base`, `secret_key_base`,
+      `otp_key_base`, `encrypted_settings_key_base`, `openid_connect_signing_key`, and `active_record_encryption` from
       `/home/git/gitlab/config/secrets.yml` (self-compiled installation) to the equivalent
       ones in `/etc/gitlab/gitlab-secrets.json` (Linux package installation).
    1. Copy the contents of `/home/git/gitlab-shell/.gitlab_shell_secret` (self-compiled installation) to `secret_token`
@@ -65,6 +66,11 @@
            "secret_key_base": "...",
            "db_key_base": "...",
            "otp_key_base": "...",
+           "encrypted_settings_key_base": "...",
+           "openid_connect_signing_key": "...",
+           "active_record_encryption_primary_key": [ "..."],
+           "active_record_encryption_deterministic_key": ["..."],
+           "active_record_encryption_key_derivation_salt": "...",
          }
          ...
        }
diff --git a/files/gitlab-cookbooks/gitlab/libraries/gitlab_rails.rb b/files/gitlab-cookbooks/gitlab/libraries/gitlab_rails.rb
index da34c9ee0d5f75b319bf81b7590aafecea237222_ZmlsZXMvZ2l0bGFiLWNvb2tib29rcy9naXRsYWIvbGlicmFyaWVzL2dpdGxhYl9yYWlscy5yYg==..6acfae1d0666073f35bc22a32acef95ae4fb2514_ZmlsZXMvZ2l0bGFiLWNvb2tib29rcy9naXRsYWIvbGlicmFyaWVzL2dpdGxhYl9yYWlscy5yYg== 100644
--- a/files/gitlab-cookbooks/gitlab/libraries/gitlab_rails.rb
+++ b/files/gitlab-cookbooks/gitlab/libraries/gitlab_rails.rb
@@ -78,6 +78,19 @@
       Gitlab['gitlab_rails']['encrypted_settings_key_base'] ||= SecretsHelper.generate_hex(64)
       Gitlab['gitlab_rails']['openid_connect_signing_key'] ||= SecretsHelper.generate_rsa(4096).to_pem
 
+      # 1. We set the following two keys as an array to support keys rotation.
+      #    The last key in the array is always used to encrypt data:
+      #    https://github.com/rails/rails/blob/v7.0.8.4/activerecord/lib/active_record/encryption/key_provider.rb#L21
+      #    while all the keys are used (in the order they're defined) to decrypt data:
+      #    https://github.com/rails/rails/blob/v7.0.8.4/activerecord/lib/active_record/encryption/cipher.rb#L26.
+      #    This allows to rotate keys by adding a new key as the last key, and start a re-encryption process that
+      #    runs in the background: https://gitlab.com/gitlab-org/gitlab/-/issues/494976
+      # 2. We use the same method and length as Rails' defaults:
+      #    https://github.com/rails/rails/blob/v7.0.8.4/activerecord/lib/active_record/railties/databases.rake#L537-L540
+      Gitlab['gitlab_rails']['active_record_encryption_primary_key'] ||= [SecretsHelper.generate_alphanumeric(32)]
+      Gitlab['gitlab_rails']['active_record_encryption_deterministic_key'] ||= [SecretsHelper.generate_alphanumeric(32)]
+      Gitlab['gitlab_rails']['active_record_encryption_key_derivation_salt'] ||= SecretsHelper.generate_alphanumeric(32)
+
       return unless Gitlab['gitlab_rails']['initial_root_password'].nil?
 
       Gitlab['gitlab_rails']['initial_root_password'] = SecretsHelper.generate_base64(32)
diff --git a/files/gitlab-cookbooks/gitlab/recipes/gitlab-rails.rb b/files/gitlab-cookbooks/gitlab/recipes/gitlab-rails.rb
index da34c9ee0d5f75b319bf81b7590aafecea237222_ZmlsZXMvZ2l0bGFiLWNvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItcmFpbHMucmI=..6acfae1d0666073f35bc22a32acef95ae4fb2514_ZmlsZXMvZ2l0bGFiLWNvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItcmFpbHMucmI= 100644
--- a/files/gitlab-cookbooks/gitlab/recipes/gitlab-rails.rb
+++ b/files/gitlab-cookbooks/gitlab/recipes/gitlab-rails.rb
@@ -217,7 +217,10 @@
               'secret_key_base' => node['gitlab']['gitlab_rails']['secret_key_base'],
               'otp_key_base' => node['gitlab']['gitlab_rails']['otp_key_base'],
               'encrypted_settings_key_base' => node['gitlab']['gitlab_rails']['encrypted_settings_key_base'],
-              'openid_connect_signing_key' => node['gitlab']['gitlab_rails']['openid_connect_signing_key']
+              'openid_connect_signing_key' => node['gitlab']['gitlab_rails']['openid_connect_signing_key'],
+              'active_record_encryption_primary_key' => node['gitlab']['gitlab_rails']['active_record_encryption_primary_key'],
+              'active_record_encryption_deterministic_key' => node['gitlab']['gitlab_rails']['active_record_encryption_deterministic_key'],
+              'active_record_encryption_key_derivation_salt' => node['gitlab']['gitlab_rails']['active_record_encryption_key_derivation_salt']
             } })
   dependent_services.each { |svc| notifies :restart, svc }
 end
diff --git a/files/gitlab-cookbooks/package/libraries/helpers/secrets_helper.rb b/files/gitlab-cookbooks/package/libraries/helpers/secrets_helper.rb
index da34c9ee0d5f75b319bf81b7590aafecea237222_ZmlsZXMvZ2l0bGFiLWNvb2tib29rcy9wYWNrYWdlL2xpYnJhcmllcy9oZWxwZXJzL3NlY3JldHNfaGVscGVyLnJi..6acfae1d0666073f35bc22a32acef95ae4fb2514_ZmlsZXMvZ2l0bGFiLWNvb2tib29rcy9wYWNrYWdlL2xpYnJhcmllcy9oZWxwZXJzL3NlY3JldHNfaGVscGVyLnJi 100644
--- a/files/gitlab-cookbooks/package/libraries/helpers/secrets_helper.rb
+++ b/files/gitlab-cookbooks/package/libraries/helpers/secrets_helper.rb
@@ -17,6 +17,10 @@
     SecureRandom.urlsafe_base64(bytes)
   end
 
+  def self.generate_alphanumeric(chars)
+    SecureRandom.alphanumeric(chars)
+  end
+
   def self.generate_rsa(bits)
     OpenSSL::PKey::RSA.new(bits)
   end
@@ -81,7 +85,10 @@
         'db_key_base' => Gitlab['gitlab_rails']['db_key_base'],
         'otp_key_base' => Gitlab['gitlab_rails']['otp_key_base'],
         'encrypted_settings_key_base' => Gitlab['gitlab_rails']['encrypted_settings_key_base'],
-        'openid_connect_signing_key' => Gitlab['gitlab_rails']['openid_connect_signing_key']
+        'openid_connect_signing_key' => Gitlab['gitlab_rails']['openid_connect_signing_key'],
+        'active_record_encryption_primary_key' => Gitlab['gitlab_rails']['active_record_encryption_primary_key'],
+        'active_record_encryption_deterministic_key' => Gitlab['gitlab_rails']['active_record_encryption_deterministic_key'],
+        'active_record_encryption_key_derivation_salt' => Gitlab['gitlab_rails']['active_record_encryption_key_derivation_salt']
       },
       'gitlab_pages' => {
         'gitlab_secret' => Gitlab['gitlab_pages']['gitlab_secret'],
diff --git a/spec/chef/cookbooks/gitlab/recipes/generate_secrets_spec.rb b/spec/chef/cookbooks/gitlab/recipes/generate_secrets_spec.rb
index da34c9ee0d5f75b319bf81b7590aafecea237222_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9nZW5lcmF0ZV9zZWNyZXRzX3NwZWMucmI=..6acfae1d0666073f35bc22a32acef95ae4fb2514_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9nZW5lcmF0ZV9zZWNyZXRzX3NwZWMucmI= 100644
--- a/spec/chef/cookbooks/gitlab/recipes/generate_secrets_spec.rb
+++ b/spec/chef/cookbooks/gitlab/recipes/generate_secrets_spec.rb
@@ -15,5 +15,6 @@
   optional_path = '/etc/mygitlab/mysecrets.json'.freeze
   hex_key = /\h{128}/.freeze
   rsa_key = /\A-----BEGIN RSA PRIVATE KEY-----\n.+\n-----END RSA PRIVATE KEY-----\n\Z/m.freeze
+  alphanumeric_key_regex = /\A[A-Za-z0-9]{32}\Z/m.freeze
   default_secrets_error_regexp = %r{You have enabled writing to the default secrets file location with package\['generate_secrets_json_file'].*}
 
@@ -18,19 +19,5 @@
   default_secrets_error_regexp = %r{You have enabled writing to the default secrets file location with package\['generate_secrets_json_file'].*}
 
-  def stub_gitlab_secrets_json(secrets)
-    allow(File).to receive(:read).with(SecretsHelper::SECRETS_FILE).and_return(JSON.generate(secrets))
-  end
-
-  def stub_check_secrets
-    rails_keys = new_secrets['gitlab_rails']
-    hex_keys = rails_keys.values_at('db_key_base', 'otp_key_base', 'secret_key_base', 'encrypted_settings_key_base')
-    rsa_keys = rails_keys.values_at('openid_connect_signing_key')
-
-    expect(rails_keys.to_a.uniq).to eq(rails_keys.to_a)
-    expect(hex_keys).to all(match(hex_key))
-    expect(rsa_keys).to all(match(rsa_key))
-  end
-
   before do
     allow(File).to receive(:directory?).and_call_original
     allow(File).to receive(:exist?).and_call_original
@@ -80,7 +67,12 @@
       it 'writes new secrets to the file, with different values for each' do
         rails_keys = new_secrets['gitlab_rails']
         hex_keys = rails_keys.values_at('db_key_base', 'otp_key_base', 'secret_key_base', 'encrypted_settings_key_base')
+        alphanumeric_keys = rails_keys.values_at(
+          'active_record_encryption_primary_key',
+          'active_record_encryption_deterministic_key',
+          'active_record_encryption_key_derivation_salt'
+        ).flatten
         rsa_keys = rails_keys.values_at('openid_connect_signing_key')
 
         expect(rails_keys.to_a.uniq).to eq(rails_keys.to_a)
         expect(hex_keys).to all(match(hex_key))
@@ -83,7 +75,8 @@
         rsa_keys = rails_keys.values_at('openid_connect_signing_key')
 
         expect(rails_keys.to_a.uniq).to eq(rails_keys.to_a)
         expect(hex_keys).to all(match(hex_key))
+        expect(alphanumeric_keys.flatten).to all(match(alphanumeric_key_regex))
         expect(rsa_keys).to all(match(rsa_key))
       end
     end
diff --git a/spec/chef/cookbooks/package/libraries/secrets_helper_spec.rb b/spec/chef/cookbooks/package/libraries/secrets_helper_spec.rb
index da34c9ee0d5f75b319bf81b7590aafecea237222_c3BlYy9jaGVmL2Nvb2tib29rcy9wYWNrYWdlL2xpYnJhcmllcy9zZWNyZXRzX2hlbHBlcl9zcGVjLnJi..6acfae1d0666073f35bc22a32acef95ae4fb2514_c3BlYy9jaGVmL2Nvb2tib29rcy9wYWNrYWdlL2xpYnJhcmllcy9zZWNyZXRzX2hlbHBlcl9zcGVjLnJi 100644
--- a/spec/chef/cookbooks/package/libraries/secrets_helper_spec.rb
+++ b/spec/chef/cookbooks/package/libraries/secrets_helper_spec.rb
@@ -5,6 +5,7 @@
   let(:chef_run) { ChefSpec::SoloRunner.new.converge('gitlab::default') }
 
   HEX_KEY = /\h{128}/.freeze
+  ALPHANUMERIC_KEY = /\A[A-Za-z0-9]{32}\Z/m.freeze
   RSA_KEY = /\A-----BEGIN RSA PRIVATE KEY-----\n.+\n-----END RSA PRIVATE KEY-----\n\Z/m.freeze
 
   def stub_gitlab_secrets_json(secrets)
@@ -50,7 +51,12 @@
       it 'writes new secrets to the file, with different values for each' do
         rails_keys = new_secrets['gitlab_rails']
         hex_keys = rails_keys.values_at('db_key_base', 'otp_key_base', 'secret_key_base', 'encrypted_settings_key_base')
+        alphanumeric_keys = rails_keys.values_at(
+          'active_record_encryption_primary_key',
+          'active_record_encryption_deterministic_key',
+          'active_record_encryption_key_derivation_salt'
+        ).flatten
         rsa_keys = rails_keys.values_at('openid_connect_signing_key')
 
         expect(rails_keys.to_a.uniq).to eq(rails_keys.to_a)
         expect(hex_keys).to all(match(HEX_KEY))
@@ -53,7 +59,8 @@
         rsa_keys = rails_keys.values_at('openid_connect_signing_key')
 
         expect(rails_keys.to_a.uniq).to eq(rails_keys.to_a)
         expect(hex_keys).to all(match(HEX_KEY))
+        expect(alphanumeric_keys.flatten).to all(match(ALPHANUMERIC_KEY))
         expect(rsa_keys).to all(match(RSA_KEY))
       end
 
@@ -224,7 +231,10 @@
             gitlab_rails: {
               secret_token: 'json_rails_secret_token',
               jws_private_key: 'json_rails_jws_private_key',
-              encrypted_settings_key_base: 'encrypted_settings_key_base'
+              encrypted_settings_key_base: 'encrypted_settings_key_base',
+              active_record_encryption_primary_key: ['primary_key'],
+              active_record_encryption_deterministic_key: ['deterministic_key'],
+              active_record_encryption_key_derivation_salt: 'key_derivation_salt'
             }
           )
 
@@ -259,7 +269,10 @@
                 'secret_key_base' => 'rb_ci_db_key_base',
                 'otp_key_base' => 'json_rails_secret_token',
                 'encrypted_settings_key_base' => 'encrypted_settings_key_base',
-                'openid_connect_signing_key' => 'json_rails_jws_private_key'
+                'openid_connect_signing_key' => 'json_rails_jws_private_key',
+                'active_record_encryption_primary_key' => ['primary_key'],
+                'active_record_encryption_deterministic_key' => ['deterministic_key'],
+                'active_record_encryption_key_derivation_salt' => 'key_derivation_salt'
               }
             }
           )