diff --git a/arkindex/images/models.py b/arkindex/images/models.py
index b27083a368043ddfec4f8dd59bfed3361cd9e37d..4f0091823e1802663497ab03ca09ea9fdec04b49 100644
--- a/arkindex/images/models.py
+++ b/arkindex/images/models.py
@@ -231,7 +231,6 @@ class Image(S3FileModelMixin, IndexableModel):
         """
         Check the image's existence and update width, height and status properties
         """
-
         url = self.url
         if not url.endswith('/'):
             url += '/'
@@ -243,7 +242,8 @@ class Image(S3FileModelMixin, IndexableModel):
 
             # Load data
             data = resp.json()
-            assert all(item in data for item in ('@id', 'width', 'height'))
+            assert all(item in data for item in ('@id', 'width', 'height', 'profile')), 'Missing required properties'
+            assert isinstance(data['profile'], list), 'Profile is not a list'
 
             # Check id
             image_id = data['@id']
@@ -262,6 +262,32 @@ class Image(S3FileModelMixin, IndexableModel):
         self.path = image_id[len(self.server.url) + 1:]
         self.width, self.height = int(data['width']), int(data['height'])
 
+        # Handle optional maxWidth and maxHeight properties in the profile description
+        # `profile` is a list with one or more Image API compliance level URIs,
+        # and optionally an object, the profile description.
+        max_width, max_height = None, None
+        if len(data['profile']) >= 2:
+            profile_desc = next((item for item in data['profile'] if isinstance(item, dict)), {})
+            max_width = int(profile_desc.get('maxWidth', 0))
+            max_height = int(profile_desc.get('maxHeight', 0))
+
+        # Special case for Harvard IDS: those properties are wrongly defined in the main object
+        if not max_width and 'maxWidth' in data:
+            max_width = int(data.get('maxWidth', 0))
+        if not max_height and 'maxHeight' in data:
+            max_height = int(data.get('maxHeight', 0))
+
+        # From the Image API specification: If maxWidth is specified and maxHeight is not,
+        # then clients should infer that maxHeight = maxWidth.
+        if max_width and not max_height:
+            max_height = max_width
+
+        # Update the image size to reflect the maximum allowed size
+        if (max_width or max_height) and (self.width > max_width or self.height > max_height):
+            ratio = min(max_width / self.width, max_height / self.height)
+            self.width = round(ratio * self.width)
+            self.height = round(ratio * self.height)
+
         self.status = S3FileStatus.Checked
         if save:
             self.save()
diff --git a/arkindex/images/tests/test_check_images.py b/arkindex/images/tests/test_check_images.py
index e1de93ae05f2298789430be34c398208991542a1..84c2f33ae53817fdcfc718829e85d7389f6186b0 100644
--- a/arkindex/images/tests/test_check_images.py
+++ b/arkindex/images/tests/test_check_images.py
@@ -7,11 +7,6 @@ from unittest.mock import patch
 
 class TestCheckImages(FixtureTestCase):
 
-    @classmethod
-    def setUpClass(cls):
-        super().setUpClass()
-        cls.check_mock = patch('arkindex.images.management.commands.check_images.Image.perform_check').start()
-
     @classmethod
     def setUpTestData(cls):
         super().setUpTestData()
@@ -25,29 +20,28 @@ class TestCheckImages(FixtureTestCase):
         # Create an image linked to zero elements
         cls.imgsrv.images.create(path='am-outside')
 
-    def tearDown(self):
-        super().tearDown()
-        self.check_mock.reset_mock()
-
-    def test_nothing(self):
+    @patch('arkindex.images.management.commands.check_images.Image.perform_check')
+    def test_nothing(self, check_mock):
         call_command(
             'check_images',
         )
-        self.assertEqual(self.check_mock.call_count, 5)
+        self.assertEqual(check_mock.call_count, 5)
 
-    def test_corpus(self):
+    @patch('arkindex.images.management.commands.check_images.Image.perform_check')
+    def test_corpus(self, check_mock):
         call_command(
             'check_images',
             corpus=self.corpus,
         )
-        self.assertEqual(self.check_mock.call_count, 4)
+        self.assertEqual(check_mock.call_count, 4)
 
-    def test_element(self):
+    @patch('arkindex.images.management.commands.check_images.Image.perform_check')
+    def test_element(self, check_mock):
         call_command(
             'check_images',
             element=[self.p1, self.p2],
         )
-        self.assertEqual(self.check_mock.call_count, 2)
+        self.assertEqual(check_mock.call_count, 2)
 
     def test_corpus_xor_element(self):
         with self.assertRaises(CommandError):
@@ -57,9 +51,10 @@ class TestCheckImages(FixtureTestCase):
                 element=[self.p1, ],
             )
 
-    def test_force(self):
+    @patch('arkindex.images.management.commands.check_images.Image.perform_check')
+    def test_force(self, check_mock):
         call_command(
             'check_images',
             force=True,
         )
-        self.assertEqual(self.check_mock.call_count, 7)
+        self.assertEqual(check_mock.call_count, 7)
diff --git a/arkindex/images/tests/test_perform_check.py b/arkindex/images/tests/test_perform_check.py
new file mode 100644
index 0000000000000000000000000000000000000000..e8436a672e5ba300a58725884a20314592786ac5
--- /dev/null
+++ b/arkindex/images/tests/test_perform_check.py
@@ -0,0 +1,149 @@
+from arkindex.project.aws import S3FileStatus
+from arkindex.project.tests import FixtureTestCase
+import requests
+import responses
+
+
+class TestImagePerformCheck(FixtureTestCase):
+
+    def setUp(self):
+        self.img = self.imgsrv.images.get(path='img1')
+
+    @responses.activate
+    def test_status(self):
+        """
+        Test Image.perform_check fails on a HTTP error code
+        """
+        responses.add(responses.GET, 'http://server/img1/info.json', status=418)
+        self.img.status = S3FileStatus.Unchecked
+        with self.assertRaises(requests.HTTPError):
+            self.img.perform_check(raise_exc=True, save=False)
+        self.assertEqual(self.img.status, S3FileStatus.Error)
+
+    @responses.activate
+    def test_required_items(self):
+        """
+        Test Image.perform_check fails on a HTTP error code
+        """
+        base_payload = {
+            '@id': 'http://server/img1',
+            'width': 42,
+            'height': 42,
+            'profile': ['http://iiif.io/api/image/2/level2.json'],
+        }
+        for key in base_payload:
+            payload = base_payload.copy()
+            del payload[key]
+            responses.add(responses.GET, 'http://server/img1/info.json', json=payload)
+            with self.assertRaises(AssertionError, msg='Missing required properties'):
+                self.img.perform_check(raise_exc=True, save=False)
+            self.assertEqual(self.img.status, S3FileStatus.Error)
+
+    @responses.activate
+    def test_check_id(self):
+        """
+        Test Image.perform_check ensures the image is on the expected server
+        """
+        responses.add(responses.GET, 'http://server/img1/info.json', json={
+            '@id': 'http://wrongserver/img1',
+            'width': 42,
+            'height': 42,
+            'profile': ['http://iiif.io/api/image/2/level2.json'],
+        })
+        self.img.status = S3FileStatus.Unchecked
+        with self.assertRaisesRegex(AssertionError, 'Image id does not start with server url'):
+            self.img.perform_check(raise_exc=True, save=False)
+        self.assertEqual(self.img.status, S3FileStatus.Error)
+
+    @responses.activate
+    def test_update_fields(self):
+        """
+        Test Image.perform_check updates the path, width and height
+        """
+        responses.add(responses.GET, 'http://server/img1/info.json', json={
+            '@id': 'http://server/img1111',
+            'width': 9000,
+            'height': 8000,
+            'profile': ['http://iiif.io/api/image/2/level2.json'],
+        })
+        self.assertEqual(self.img.path, 'img1')
+        self.assertEqual(self.img.width, 1000)
+        self.assertEqual(self.img.height, 1000)
+        self.img.status = S3FileStatus.Unchecked
+
+        self.img.perform_check(raise_exc=True, save=False)
+
+        self.assertEqual(self.img.status, S3FileStatus.Checked)
+        self.assertEqual(self.img.path, 'img1111')
+        self.assertEqual(self.img.width, 9000)
+        self.assertEqual(self.img.height, 8000)
+
+    @responses.activate
+    def test_max_size(self):
+        """
+        Test Image.perform_check handles the IIIF maxWidth and maxHeight optional properties
+        """
+        responses.add(responses.GET, 'http://server/img1/info.json', json={
+            '@id': 'http://server/img1',
+            'width': 9000,
+            'height': 8000,
+            'profile': [
+                'http://iiif.io/api/image/2/level2.json',
+                {
+                    "maxWidth": 4500,
+                    "maxHeight": 10000,
+                }
+            ],
+        })
+        self.img.status = S3FileStatus.Unchecked
+        self.img.perform_check(raise_exc=True, save=False)
+
+        self.assertEqual(self.img.status, S3FileStatus.Checked)
+        self.assertEqual(self.img.width, 4500)
+        self.assertEqual(self.img.height, 4000)
+
+    @responses.activate
+    def test_max_height_deduced(self):
+        """
+        Test Image.perform_check assumes maxHeight is equal to maxWidth
+        when maxHeight is missing and maxWidth is defined
+        """
+        responses.add(responses.GET, 'http://server/img1/info.json', json={
+            '@id': 'http://server/img1',
+            'width': 1000,  # width < maxWidth
+            'height': 8000,  # height > maxWidth
+            'profile': [
+                'http://iiif.io/api/image/2/level2.json',
+                {
+                    "maxWidth": 4000,
+                }
+            ],
+        })
+        self.img.status = S3FileStatus.Unchecked
+        self.img.perform_check(raise_exc=True, save=False)
+
+        self.assertEqual(self.img.status, S3FileStatus.Checked)
+        self.assertEqual(self.img.width, 500)
+        self.assertEqual(self.img.height, 4000)
+
+    @responses.activate
+    def test_harvard_non_conform(self):
+        """
+        Test Image.perform_check falls back to properties from the Image Information object
+        when they are not defined in the profile description object or when it is missing.
+        """
+        responses.add(responses.GET, 'http://server/img1/info.json', json={
+            '@id': 'http://server/img1',
+            'width': 9001,
+            'maxWidth': 9000,
+            'height': 9001,
+            'maxHeight': 8999,
+            'profile': ['http://iiif.io/api/image/2/level2.json'],
+        })
+
+        self.img.status = S3FileStatus.Unchecked
+        self.img.perform_check(raise_exc=True, save=False)
+
+        self.assertEqual(self.img.status, S3FileStatus.Checked)
+        self.assertEqual(self.img.width, 8999)
+        self.assertEqual(self.img.height, 8999)
diff --git a/tests-requirements.txt b/tests-requirements.txt
index 243c31c1b200b537ce04f5aa4dc6d2033824c9e3..6c1358c164ff9bac9813f2de73bb6a8f2e9d9b3d 100644
--- a/tests-requirements.txt
+++ b/tests-requirements.txt
@@ -3,3 +3,4 @@ tripoli
 django-nose
 coverage
 uritemplate==3
+responses