diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index f7b1c2f7567e61fd2945685b7b19d64846504fb6..a5e3b8b24e7b3ebdd8e4a340cb7ae5a737fcee3f 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -1146,14 +1146,22 @@ production: &base
     #   # Use multipart uploads when file size reaches 100MB, see
     #   #  http://docs.aws.amazon.com/AmazonS3/latest/dev/uploadobjusingmpu.html
     #   multipart_chunk_size: 104857600
-    #   # Turns on AWS Server-Side Encryption with Amazon S3-Managed Keys for backups, this is optional
-    #   # encryption: 'AES256'
+    #   # Specifies Amazon S3 storage class to use for backups (optional)
+    #   # storage_class: 'STANDARD'
     #   # Turns on AWS Server-Side Encryption with Amazon Customer-Provided Encryption Keys for backups, this is optional
-    #   #   This should be set to the 256-bit encryption key for Amazon S3 to use to encrypt or decrypt your data.
-    #   #   'encryption' must also be set in order for this to have any effect.
+    #   #   'encryption' must be set in order for this to have any effect.
+    #   #   'encryption_key' should be set to the 256-bit encryption key for Amazon S3 to use to encrypt or decrypt your data.
+    #   # encryption: 'AES256'
     #   # encryption_key: '<key>'
-    #   # Specifies Amazon S3 storage class to use for backups, this is optional
-    #   # storage_class: 'STANDARD'
+    #   #
+    #   # Turns on AWS Server-Side Encryption with Amazon S3-Managed keys (optional)
+    #   # https://docs.aws.amazon.com/AmazonS3/latest/userguide/serv-side-encryption.html
+    #   # For SSE-S3, set 'server_side_encryption' to 'AES256'.
+    #   # For SS3-KMS, set 'server_side_encryption' to 'aws:kms'. Set
+    #   # 'server_side_encryption_kms_key_id' to the ARN of customer master key.
+    #   # storage_options:
+    #   #   server_side_encryption: 'aws:kms'
+    #   #   server_side_encryption_kms_key_id: 'arn:aws:kms:YOUR-KEY-ID-HERE'
 
   ## Pseudonymizer exporter
   pseudonymizer:
diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb
index 52810b0fb35940ab6658b701a4aaa82be2cc5dc6..6c5350082e888b6e90f57556d4a1e836981b7266 100644
--- a/lib/backup/manager.rb
+++ b/lib/backup/manager.rb
@@ -47,10 +47,12 @@ def upload
         return
       end
 
-      directory = connect_to_remote_directory(Gitlab.config.backup.upload)
+      directory = connect_to_remote_directory
+      upload = directory.files.create(create_attributes)
 
-      if directory.files.create(create_attributes)
+      if upload
         progress.puts "done".color(:green)
+        upload
       else
         puts "uploading backup to #{remote_directory} failed".color(:red)
         raise Backup::Error, 'Backup failed'
@@ -206,11 +208,16 @@ def available_timestamps
       @backup_file_list.map {|item| item.gsub("#{FILE_NAME_SUFFIX}", "")}
     end
 
-    def connect_to_remote_directory(options)
-      config = ObjectStorage::Config.new(options)
-      config.load_provider
+    def object_storage_config
+      @object_storage_config ||= begin
+        config = ObjectStorage::Config.new(Gitlab.config.backup.upload)
+        config.load_provider
+        config
+      end
+    end
 
-      connection = ::Fog::Storage.new(config.credentials)
+    def connect_to_remote_directory
+      connection = ::Fog::Storage.new(object_storage_config.credentials)
 
       # We only attempt to create the directory for local backups. For AWS
       # and other cloud providers, we cannot guarantee the user will have
@@ -280,10 +287,8 @@ def create_attributes
         key: remote_target,
         body: File.open(File.join(backup_path, tar_file)),
         multipart_chunk_size: Gitlab.config.backup.upload.multipart_chunk_size,
-        encryption: Gitlab.config.backup.upload.encryption,
-        encryption_key: Gitlab.config.backup.upload.encryption_key,
         storage_class: Gitlab.config.backup.upload.storage_class
-      }
+      }.merge(encryption_attributes)
 
       # Google bucket-only policies prevent setting an ACL. In any case, by default,
       # all objects are set to the default ACL, which is project-private:
@@ -293,6 +298,19 @@ def create_attributes
       attrs
     end
 
+    def encryption_attributes
+      return object_storage_config.fog_attributes if object_storage_config.aws_server_side_encryption_enabled?
+
+      # Use customer-managed keys. Also, this preserves
+      # backward-compatibility for existing usages of `SSE-S3` that
+      # don't set `backup.upload.storage_options.server_side_encryption`
+      # to `'AES256'`.
+      {
+        encryption_key: Gitlab.config.backup.upload.encryption_key,
+        encryption: Gitlab.config.backup.upload.encryption
+      }
+    end
+
     def google_provider?
       Gitlab.config.backup.upload.connection&.provider&.downcase == 'google'
     end
diff --git a/lib/object_storage/config.rb b/lib/object_storage/config.rb
index f933d4e48661a48efdbfa23dbda8e79e49487ce4..82d9fc070437cd253bc607c4ccd5e6760d869bbf 100644
--- a/lib/object_storage/config.rb
+++ b/lib/object_storage/config.rb
@@ -84,13 +84,16 @@ def azure?
 
     def fog_attributes
       @fog_attributes ||= begin
-        return {} unless enabled? && aws?
-        return {} unless server_side_encryption.present?
+        return {} unless aws_server_side_encryption_enabled?
 
         aws_server_side_encryption_headers.compact
       end
     end
 
+    def aws_server_side_encryption_enabled?
+      aws? && server_side_encryption.present?
+    end
+
     private
 
     # This returns a Hash of HTTP encryption headers to send along to S3.
diff --git a/spec/lib/backup/manager_spec.rb b/spec/lib/backup/manager_spec.rb
index 2cc1bf41d18da84c75f8203051dc141b78ec9766..32eea82cfdfa16c620dfe0227ed33514e2a01b47 100644
--- a/spec/lib/backup/manager_spec.rb
+++ b/spec/lib/backup/manager_spec.rb
@@ -432,6 +432,77 @@
       end
     end
 
+    context 'with AWS with server side encryption' do
+      let(:connection) { ::Fog::Storage.new(Gitlab.config.backup.upload.connection.symbolize_keys) }
+      let(:encryption_key) { nil }
+      let(:encryption) { nil }
+      let(:storage_options) { nil }
+
+      before do
+        stub_backup_setting(
+          upload: {
+            connection: {
+              provider: 'AWS',
+              aws_access_key_id: 'AWS_ACCESS_KEY_ID',
+              aws_secret_access_key: 'AWS_SECRET_ACCESS_KEY'
+            },
+            remote_directory: 'directory',
+            multipart_chunk_size: Gitlab.config.backup.upload.multipart_chunk_size,
+            encryption: encryption,
+            encryption_key: encryption_key,
+            storage_options: storage_options,
+            storage_class: nil
+          }
+        )
+
+        connection.directories.create(key: Gitlab.config.backup.upload.remote_directory)
+      end
+
+      context 'with SSE-S3 without using storage_options' do
+        let(:encryption) { 'AES256' }
+
+        it 'sets encryption attributes' do
+          result = subject.upload
+
+          expect(result.key).to be_present
+          expect(result.encryption).to eq('AES256')
+          expect(result.encryption_key).to be_nil
+          expect(result.kms_key_id).to be_nil
+        end
+      end
+
+      context 'with SSE-C (customer-provided keys) options' do
+        let(:encryption) { 'AES256' }
+        let(:encryption_key) { SecureRandom.hex }
+
+        it 'sets encryption attributes' do
+          result = subject.upload
+
+          expect(result.key).to be_present
+          expect(result.encryption).to eq(encryption)
+          expect(result.encryption_key).to eq(encryption_key)
+          expect(result.kms_key_id).to be_nil
+        end
+      end
+
+      context 'with SSE-KMS options' do
+        let(:storage_options) do
+          {
+            server_side_encryption: 'aws:kms',
+            server_side_encryption_kms_key_id: 'arn:aws:kms:12345'
+          }
+        end
+
+        it 'sets encryption attributes' do
+          result = subject.upload
+
+          expect(result.key).to be_present
+          expect(result.encryption).to eq('aws:kms')
+          expect(result.kms_key_id).to eq('arn:aws:kms:12345')
+        end
+      end
+    end
+
     context 'with Google provider' do
       before do
         stub_backup_setting(
diff --git a/spec/lib/object_storage/config_spec.rb b/spec/lib/object_storage/config_spec.rb
index 0ead2a1d2699e7518299f8b0b9a8e8fac4473c4e..21b8a44b3d637b086ff16cfc10862d6a021a4a36 100644
--- a/spec/lib/object_storage/config_spec.rb
+++ b/spec/lib/object_storage/config_spec.rb
@@ -188,6 +188,7 @@
   end
 
   context 'with SSE-KMS enabled' do
+    it { expect(subject.aws_server_side_encryption_enabled?).to be true }
     it { expect(subject.server_side_encryption).to eq('AES256') }
     it { expect(subject.server_side_encryption_kms_key_id).to eq('arn:aws:12345') }
     it { expect(subject.fog_attributes.keys).to match_array(%w(x-amz-server-side-encryption x-amz-server-side-encryption-aws-kms-key-id)) }
@@ -196,6 +197,7 @@
   context 'with only server side encryption enabled' do
     let(:storage_options) { { server_side_encryption: 'AES256' } }
 
+    it { expect(subject.aws_server_side_encryption_enabled?).to be true }
     it { expect(subject.server_side_encryption).to eq('AES256') }
     it { expect(subject.server_side_encryption_kms_key_id).to be_nil }
     it { expect(subject.fog_attributes).to eq({ 'x-amz-server-side-encryption' => 'AES256' }) }
@@ -204,6 +206,7 @@
   context 'without encryption enabled' do
     let(:storage_options) { {} }
 
+    it { expect(subject.aws_server_side_encryption_enabled?).to be false }
     it { expect(subject.server_side_encryption).to be_nil }
     it { expect(subject.server_side_encryption_kms_key_id).to be_nil }
     it { expect(subject.fog_attributes).to eq({}) }
@@ -215,6 +218,5 @@
     end
 
     it { expect(subject.enabled?).to be false }
-    it { expect(subject.fog_attributes).to eq({}) }
   end
 end