diff --git a/internal/objectstore/multipart.go b/internal/objectstore/multipart.go
index 09e2f12308ede910f54656089a2c2b352fcf220f..9f8bce4f613075083f15226f3ce2ee1514ae62c1 100644
--- a/internal/objectstore/multipart.go
+++ b/internal/objectstore/multipart.go
@@ -155,11 +155,8 @@ func (m *Multipart) complete(cmu *CompleteMultipartUpload) error {
 	}
 
 	m.extractETag(result.ETag)
-	if err := m.verifyETag(cmu); err != nil {
-		return fmt.Errorf("ETag verification failure: %v", err)
-	}
 
-	return nil
+	return m.verifyETag(cmu)
 }
 
 func (m *Multipart) verifyETag(cmu *CompleteMultipartUpload) error {
@@ -167,11 +164,8 @@ func (m *Multipart) verifyETag(cmu *CompleteMultipartUpload) error {
 	if err != nil {
 		return err
 	}
-	if expectedChecksum != m.etag {
-		return fmt.Errorf("got %q expected %q", m.etag, expectedChecksum)
-	}
 
-	return nil
+	return compareMD5(expectedChecksum, m.etag)
 }
 
 func (m *Multipart) readAndUploadOnePart(partURL string, putHeaders map[string]string, src io.Reader, partNumber int) (*completeMultipartUploadPart, error) {
diff --git a/internal/objectstore/multipart_test.go b/internal/objectstore/multipart_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..c0a8b46f49af77898d6492d7f23cf399e3b0814c
--- /dev/null
+++ b/internal/objectstore/multipart_test.go
@@ -0,0 +1,69 @@
+package objectstore_test
+
+import (
+	"context"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"net/http/httptest"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/require"
+
+	"gitlab.com/gitlab-org/gitlab-workhorse/internal/objectstore"
+	"gitlab.com/gitlab-org/gitlab-workhorse/internal/objectstore/test"
+)
+
+func TestMultipartUploadWithUpcaseETags(t *testing.T) {
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+
+	var putCnt, postCnt int
+
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		_, err := ioutil.ReadAll(r.Body)
+		require.NoError(t, err)
+		defer r.Body.Close()
+
+		// Part upload request
+		if r.Method == "PUT" {
+			putCnt++
+
+			w.Header().Set("ETag", strings.ToUpper(test.ObjectMD5))
+		}
+
+		// POST with CompleteMultipartUpload request
+		if r.Method == "POST" {
+			expectedETag := "6e6b164c392b04bfbb82368179d9ade2-1"
+			completeBody := fmt.Sprintf(`<CompleteMultipartUploadResult>
+                                                       <Bucket>test-bucket</Bucket>
+			                               <ETag>%s</ETag>
+  	                                             </CompleteMultipartUploadResult>`,
+				strings.ToUpper(expectedETag))
+			postCnt++
+
+			w.Write([]byte(completeBody))
+		}
+	}))
+	defer ts.Close()
+
+	deadline := time.Now().Add(testTimeout)
+
+	m, err := objectstore.NewMultipart(ctx,
+		[]string{ts.URL},    // a single presigned part URL
+		ts.URL,              // the complete multipart upload URL
+		"",                  // no abort
+		"",                  // no delete
+		map[string]string{}, // no custom headers
+		deadline,
+		test.ObjectSize) // parts size equal to the whole content. Only 1 part
+	require.NoError(t, err)
+
+	_, err = m.Write([]byte(test.ObjectContent))
+	require.NoError(t, err)
+	require.NoError(t, m.Close())
+	require.Equal(t, 1, putCnt, "1 part expected")
+	require.Equal(t, 1, postCnt, "1 complete multipart upload expected")
+}
diff --git a/internal/objectstore/object.go b/internal/objectstore/object.go
index 70e060ef403b3c1458df7a9bba2b604dfd213329..169d76d7270958d0fdfff947d3bd6368a528e42f 100644
--- a/internal/objectstore/object.go
+++ b/internal/objectstore/object.go
@@ -7,6 +7,7 @@ import (
 	"io/ioutil"
 	"net"
 	"net/http"
+	"strings"
 	"time"
 
 	"gitlab.com/gitlab-org/labkit/correlation"
@@ -124,10 +125,7 @@ func newObject(ctx context.Context, putURL, deleteURL string, putHeaders map[str
 		}
 
 		o.extractETag(resp.Header.Get("ETag"))
-		if o.etag != o.md5Sum() {
-			o.uploadError = fmt.Errorf("ETag mismatch. expected %q got %q", o.md5Sum(), o.etag)
-			return
-		}
+		o.uploadError = compareMD5(o.md5Sum(), o.etag)
 	}()
 
 	return o, nil
@@ -136,3 +134,11 @@ func newObject(ctx context.Context, putURL, deleteURL string, putHeaders map[str
 func (o *Object) delete() {
 	o.syncAndDelete(o.DeleteURL)
 }
+
+func compareMD5(local, remote string) error {
+	if !strings.EqualFold(local, remote) {
+		return fmt.Errorf("ETag mismatch. expected %q got %q", local, remote)
+	}
+
+	return nil
+}
diff --git a/internal/objectstore/object_test.go b/internal/objectstore/object_test.go
index e5f9a562caf1c428f0bfc55cfe8e1184544e605e..bd367b2769fc4dcb622fc5c8d1c35b4c5dabe41e 100644
--- a/internal/objectstore/object_test.go
+++ b/internal/objectstore/object_test.go
@@ -18,10 +18,12 @@ import (
 
 const testTimeout = 10 * time.Second
 
-func testObjectUploadNoErrors(t *testing.T, useDeleteURL bool, contentType string) {
+type osFactory func() (*test.ObjectstoreStub, *httptest.Server)
+
+func testObjectUploadNoErrors(t *testing.T, startObjectStore osFactory, useDeleteURL bool, contentType string) {
 	assert := assert.New(t)
 
-	osStub, ts := test.StartObjectStore()
+	osStub, ts := startObjectStore()
 	defer ts.Close()
 
 	objectURL := ts.URL + test.ObjectPath
@@ -77,9 +79,26 @@ func testObjectUploadNoErrors(t *testing.T, useDeleteURL bool, contentType strin
 }
 
 func TestObjectUpload(t *testing.T) {
-	t.Run("with delete URL", func(t *testing.T) { testObjectUploadNoErrors(t, true, "application/octet-stream") })
-	t.Run("without delete URL", func(t *testing.T) { testObjectUploadNoErrors(t, false, "application/octet-stream") })
-	t.Run("with custom content type", func(t *testing.T) { testObjectUploadNoErrors(t, false, "image/jpeg") })
+	t.Run("with delete URL", func(t *testing.T) {
+		testObjectUploadNoErrors(t, test.StartObjectStore, true, "application/octet-stream")
+	})
+	t.Run("without delete URL", func(t *testing.T) {
+		testObjectUploadNoErrors(t, test.StartObjectStore, false, "application/octet-stream")
+	})
+	t.Run("with custom content type", func(t *testing.T) {
+		testObjectUploadNoErrors(t, test.StartObjectStore, false, "image/jpeg")
+	})
+	t.Run("with upcase ETAG", func(t *testing.T) {
+		factory := func() (*test.ObjectstoreStub, *httptest.Server) {
+			md5s := map[string]string{
+				test.ObjectPath: strings.ToUpper(test.ObjectMD5),
+			}
+
+			return test.StartObjectStoreWithCustomMD5(md5s)
+		}
+
+		testObjectUploadNoErrors(t, factory, false, "application/octet-stream")
+	})
 }
 
 func TestObjectUpload404(t *testing.T) {