diff --git a/django/core/management/commands/migrate.py b/django/core/management/commands/migrate.py
index b6c8748bff..4c8e008374 100644
--- a/django/core/management/commands/migrate.py
+++ b/django/core/management/commands/migrate.py
@@ -175,7 +175,6 @@ class Command(BaseCommand):
         if self.verbosity >= 1:
             self.stdout.write(self.style.MIGRATE_HEADING("Running migrations:"))
         if not plan:
-            executor.check_replacements()
             if self.verbosity >= 1:
                 self.stdout.write("  No migrations to apply.")
                 # If there's changes that aren't in migrations yet, tell them how to fix it.
@@ -194,14 +193,15 @@ class Command(BaseCommand):
                         "migrations, and then re-run 'manage.py migrate' to "
                         "apply them."
                     ))
-            post_migrate_apps = pre_migrate_apps
+            fake = False
+            fake_initial = False
         else:
             fake = options['fake']
             fake_initial = options['fake_initial']
-            post_migrate_project_state = executor.migrate(
-                targets, plan, fake=fake, fake_initial=fake_initial
-            )
-            post_migrate_apps = post_migrate_project_state.apps
+        post_migrate_project_state = executor.migrate(
+            targets, plan, fake=fake, fake_initial=fake_initial
+        )
+        post_migrate_apps = post_migrate_project_state.apps
 
         # Re-render models of real apps to include relationships now that
         # we've got a final state. This wouldn't be necessary if real apps
diff --git a/django/db/migrations/executor.py b/django/db/migrations/executor.py
index fb9c7a93a4..11a96ec81b 100644
--- a/django/db/migrations/executor.py
+++ b/django/db/migrations/executor.py
@@ -82,9 +82,15 @@ class MigrationExecutor(object):
         all_backwards = all(backwards for mig, backwards in plan)
 
         if not plan:
-            # Nothing to do for an empty plan, except for building the post
-            # migrate project state
+            # The resulting state should include applied migrations.
             state = self._create_project_state()
+            applied_migrations = {
+                self.loader.graph.nodes[key] for key in self.loader.applied_migrations
+                if key in self.loader.graph.nodes
+            }
+            for migration, _ in full_plan:
+                if migration in applied_migrations:
+                    migration.mutate_state(state, preserve=False)
         elif all_forwards == all_backwards:
             # This should only happen if there's a mixed plan
             raise InvalidMigrationPlan(
diff --git a/docs/releases/1.10.1.txt b/docs/releases/1.10.1.txt
index 3e73483e37..46188aa2da 100644
--- a/docs/releases/1.10.1.txt
+++ b/docs/releases/1.10.1.txt
@@ -68,3 +68,7 @@ Bugfixes
 
 * Added the database alias to the ``InconsistentMigrationHistory`` message
   raised by ``makemigrations`` and ``migrate`` (:ticket:`27089`).
+
+* Fixed the creation of ``ContentType`` and ``Permission`` objects for models
+  of applications without migrations when calling the ``migrate`` command with
+  no migrations to apply (:ticket:`27044`).
diff --git a/tests/migrate_signals/tests.py b/tests/migrate_signals/tests.py
index d422ab84b4..bb3ea0ccc9 100644
--- a/tests/migrate_signals/tests.py
+++ b/tests/migrate_signals/tests.py
@@ -113,3 +113,13 @@ class MigrateSignalTests(TransactionTestCase):
             [model._meta.label for model in post_migrate_receiver.call_args['apps'].get_models()],
             ['migrate_signals.Signal']
         )
+        # Migrating with an empty plan.
+        post_migrate_receiver = Receiver(signals.post_migrate)
+        management.call_command(
+            'migrate', database=MIGRATE_DATABASE, verbosity=MIGRATE_VERBOSITY,
+            interactive=MIGRATE_INTERACTIVE, stdout=stdout,
+        )
+        self.assertEqual(
+            [model._meta.label for model in post_migrate_receiver.call_args['apps'].get_models()],
+            ['migrate_signals.Signal']
+        )
diff --git a/tests/migrations/test_executor.py b/tests/migrations/test_executor.py
index b61f51f457..847b11c088 100644
--- a/tests/migrations/test_executor.py
+++ b/tests/migrations/test_executor.py
@@ -168,6 +168,14 @@ class ExecutorTests(MigrationTestBase):
             ("migrations2", "0001_initial"),
         ])
         self.assertEqual(plan, [])
+        # The resulting state should include applied migrations.
+        state = executor.migrate([
+            ("migrations", "0002_second"),
+            ("migrations2", "0001_initial"),
+        ])
+        self.assertIn(('migrations', 'book'), state.models)
+        self.assertIn(('migrations', 'author'), state.models)
+        self.assertIn(('migrations2', 'otherauthor'), state.models)
         # Erase all the fake records
         executor.recorder.record_unapplied("migrations2", "0001_initial")
         executor.recorder.record_unapplied("migrations", "0002_second")