diff --git a/core/lib/Drupal/Core/DependencyInjection/YamlFileLoader.php b/core/lib/Drupal/Core/DependencyInjection/YamlFileLoader.php
index 33168c007d477c4e9f225a2b960097884821248b..0c35209135bc4d0d110f9243c900c9356da8c046 100644
--- a/core/lib/Drupal/Core/DependencyInjection/YamlFileLoader.php
+++ b/core/lib/Drupal/Core/DependencyInjection/YamlFileLoader.php
@@ -31,6 +31,11 @@
  */
 class YamlFileLoader
 {
+    private const DEFAULTS_KEYWORDS = [
+        'public' => 'public',
+        'tags' => 'tags',
+        'autowire' => 'autowire',
+    ];
 
     /**
      * @var \Drupal\Core\DependencyInjection\ContainerBuilder $container
@@ -44,7 +49,6 @@ class YamlFileLoader
      */
     protected $fileCache;
 
-
     public function __construct(ContainerBuilder $container)
     {
         $this->container = $container;
@@ -121,12 +125,71 @@ private function parseDefinitions($content, $file)
             $basename = basename($file);
             [$provider, ] = explode('.', $basename, 2);
         }
+        $defaults = $this->parseDefaults($content, $file);
+        $defaults['tags'][] = [
+            'name' => '_provider',
+            'provider' => $provider
+        ];
         foreach ($content['services'] as $id => $service) {
-            if (is_array($service)) {
-              $service['tags'][] = ['name' => '_provider', 'provider' => $provider];
+            $this->parseDefinition($id, $service, $file, $defaults);
+        }
+    }
+
+    /**
+     * @param array  $content
+     * @param string $file
+     *
+     * @return array
+     *
+     * @throws InvalidArgumentException
+     */
+    private function parseDefaults(array &$content, string $file): array
+    {
+        if (!\array_key_exists('_defaults', $content['services'])) {
+            return [];
+        }
+        $defaults = $content['services']['_defaults'];
+        unset($content['services']['_defaults']);
+
+        if (!\is_array($defaults)) {
+            throw new InvalidArgumentException(sprintf('Service "_defaults" key must be an array, "%s" given in "%s".', \gettype($defaults), $file));
+        }
+
+        foreach ($defaults as $key => $default) {
+            if (!isset(self::DEFAULTS_KEYWORDS[$key])) {
+                throw new InvalidArgumentException(sprintf('The configuration key "%s" cannot be used to define a default value in "%s". Allowed keys are "%s".', $key, $file, implode('", "', self::DEFAULTS_KEYWORDS)));
             }
-            $this->parseDefinition($id, $service, $file);
         }
+
+        if (isset($defaults['tags'])) {
+            if (!\is_array($tags = $defaults['tags'])) {
+                throw new InvalidArgumentException(sprintf('Parameter "tags" in "_defaults" must be an array in "%s". Check your YAML syntax.', $file));
+            }
+
+            foreach ($tags as $tag) {
+                if (!\is_array($tag)) {
+                    $tag = ['name' => $tag];
+                }
+
+                if (!isset($tag['name'])) {
+                    throw new InvalidArgumentException(sprintf('A "tags" entry in "_defaults" is missing a "name" key in "%s".', $file));
+                }
+                $name = $tag['name'];
+                unset($tag['name']);
+
+                if (!\is_string($name) || '' === $name) {
+                    throw new InvalidArgumentException(sprintf('The tag name in "_defaults" must be a non-empty string in "%s".', $file));
+                }
+
+                foreach ($tag as $attribute => $value) {
+                    if (!is_scalar($value) && null !== $value) {
+                        throw new InvalidArgumentException(sprintf('Tag "%s", attribute "%s" in "_defaults" must be of a scalar-type in "%s". Check your YAML syntax.', $name, $attribute, $file));
+                    }
+                }
+            }
+        }
+
+        return $defaults;
     }
 
     /**
@@ -135,14 +198,18 @@ private function parseDefinitions($content, $file)
      * @param string $id
      * @param array $service
      * @param string $file
+     * @param array $defaults
      *
      * @throws InvalidArgumentException
      *   When tags are invalid.
      */
-    private function parseDefinition($id, $service, $file)
+    private function parseDefinition(string $id, $service, string $file, array $defaults)
     {
-        if (is_string($service) && 0 === strpos($service, '@')) {
-            $this->container->setAlias($id, substr($service, 1));
+        if (\is_string($service) && 0 === strpos($service, '@')) {
+            $this->container->setAlias($id, $alias = new Alias(substr($service, 1)));
+            if (isset($defaults['public'])) {
+                $alias->setPublic($defaults['public']);
+            }
 
             return;
         }
@@ -156,10 +223,11 @@ private function parseDefinition($id, $service, $file)
         }
 
         if (isset($service['alias'])) {
-            $alias = $this->container->setAlias($id, new Alias($service['alias']));
-
-            if (array_key_exists('public', $service)) {
+            $this->container->setAlias($id, $alias = new Alias($service['alias']));
+            if (isset($service['public'])) {
                 $alias->setPublic($service['public']);
+            } elseif (isset($defaults['public'])) {
+                $alias->setPublic($defaults['public']);
             }
 
             if (array_key_exists('deprecated', $service)) {
@@ -176,6 +244,18 @@ private function parseDefinition($id, $service, $file)
             $definition = new Definition();
         }
 
+        // Drupal services are public by default.
+        $definition->setPublic(true);
+
+        if (isset($defaults['public'])) {
+            $definition->setPublic($defaults['public']);
+        }
+        if (isset($defaults['autowire'])) {
+            $definition->setAutowired($defaults['autowire']);
+        }
+
+        $definition->setChanges([]);
+
         if (isset($service['class'])) {
             $definition->setClass($service['class']);
         }
@@ -195,9 +275,6 @@ private function parseDefinition($id, $service, $file)
         if (isset($service['public'])) {
             $definition->setPublic($service['public']);
         }
-        else {
-            $definition->setPublic(true);
-        }
 
         if (isset($service['abstract'])) {
             $definition->setAbstract($service['abstract']);
@@ -271,31 +348,37 @@ private function parseDefinition($id, $service, $file)
             }
         }
 
-        if (isset($service['tags'])) {
-            if (!is_array($service['tags'])) {
-                throw new InvalidArgumentException(sprintf('Parameter "tags" must be an array for service "%s" in %s. Check your YAML syntax.', $id, $file));
-            }
+        $tags = $service['tags'] ?? [];
+        if (!\is_array($tags)) {
+            throw new InvalidArgumentException(sprintf('Parameter "tags" must be an array for service "%s" in "%s". Check your YAML syntax.', $id, $file));
+        }
 
-            foreach ($service['tags'] as $tag) {
-                if (!is_array($tag)) {
-                    throw new InvalidArgumentException(sprintf('A "tags" entry must be an array for service "%s" in %s. Check your YAML syntax.', $id, $file));
-                }
+        if (isset($defaults['tags'])) {
+            $tags = array_merge($tags, $defaults['tags']);
+        }
 
-                if (!isset($tag['name'])) {
-                    throw new InvalidArgumentException(sprintf('A "tags" entry is missing a "name" key for service "%s" in %s.', $id, $file));
-                }
+        foreach ($tags as $tag) {
+            if (!\is_array($tag)) {
+                $tag = ['name' => $tag];
+            }
 
-                $name = $tag['name'];
-                unset($tag['name']);
+            if (!isset($tag['name'])) {
+                throw new InvalidArgumentException(sprintf('A "tags" entry is missing a "name" key for service "%s" in "%s".', $id, $file));
+            }
+            $name = $tag['name'];
+            unset($tag['name']);
 
-                foreach ($tag as $attribute => $value) {
-                    if (!is_scalar($value) && null !== $value) {
-                        throw new InvalidArgumentException(sprintf('A "tags" attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s" in %s. Check your YAML syntax.', $id, $name, $attribute, $file));
-                    }
-                }
+            if (!\is_string($name) || '' === $name) {
+                throw new InvalidArgumentException(sprintf('The tag name for service "%s" in "%s" must be a non-empty string.', $id, $file));
+            }
 
-                $definition->addTag($name, $tag);
+            foreach ($tag as $attribute => $value) {
+                if (!is_scalar($value) && null !== $value) {
+                    throw new InvalidArgumentException(sprintf('A "tags" attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s" in "%s". Check your YAML syntax.', $id, $name, $attribute, $file));
+                }
             }
+
+            $definition->addTag($name, $tag);
         }
 
         if (isset($service['decorates'])) {
diff --git a/core/modules/system/tests/modules/services_defaults_test/services_defaults_test.info.yml b/core/modules/system/tests/modules/services_defaults_test/services_defaults_test.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2c81b06d451203b733341c62f9e7356eea3488a9
--- /dev/null
+++ b/core/modules/system/tests/modules/services_defaults_test/services_defaults_test.info.yml
@@ -0,0 +1,5 @@
+name: 'Services _defaults test'
+type: module
+description: 'Support module for services _defaults'
+package: Testing
+version: VERSION
diff --git a/core/modules/system/tests/modules/services_defaults_test/services_defaults_test.services.yml b/core/modules/system/tests/modules/services_defaults_test/services_defaults_test.services.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7206e711bb3c7f09552a3ffdcb3ba5197c09fb7f
--- /dev/null
+++ b/core/modules/system/tests/modules/services_defaults_test/services_defaults_test.services.yml
@@ -0,0 +1,29 @@
+services:
+  _defaults:
+    autowire: true
+    public: false
+    tags:
+      - 'foo.tag1'
+      - { name: bar.tag2, test: 123 }
+      - { name: bar.tag3, value: null }
+  # Use an alias so the interface autowiring is tested.
+  Drupal\services_defaults_test\TestInjectionInterface: '@Drupal\services_defaults_test\TestInjection'
+  # A service that implements TestInjectionInterface.
+  Drupal\services_defaults_test\TestInjection:
+    public: true
+  Drupal\services_defaults_test\TestInjection2:
+    public: true
+    tags:
+      - 'zee.bang'
+      - { name: bar.tag2, test: 321 }
+  Drupal\services_defaults_test\TestService:
+    public: true
+  Drupal\services_defaults_test\TestPrivateService: ~
+  'services_default_test.no_autowire':
+    class: 'Drupal\services_defaults_test\TestService'
+    autowire: false
+    arguments: ['@services_default_test.no_autowire.arg', '@Drupal\services_defaults_test\TestInjection2']
+    public: true
+  'services_default_test.no_autowire.arg':
+    class: 'Drupal\services_defaults_test\TestInjection'
+    public: true
diff --git a/core/modules/system/tests/modules/services_defaults_test/src/TestInjection.php b/core/modules/system/tests/modules/services_defaults_test/src/TestInjection.php
new file mode 100644
index 0000000000000000000000000000000000000000..55ced1c887c19800ac1cf71120ea77a2d190e6a8
--- /dev/null
+++ b/core/modules/system/tests/modules/services_defaults_test/src/TestInjection.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Drupal\services_defaults_test;
+
+/**
+ * A service that is injected via default autowiring.
+ */
+class TestInjection implements TestInjectionInterface {
+}
diff --git a/core/modules/system/tests/modules/services_defaults_test/src/TestInjection2.php b/core/modules/system/tests/modules/services_defaults_test/src/TestInjection2.php
new file mode 100644
index 0000000000000000000000000000000000000000..150f505dfd8e380af58072fef42b70a6f02a9061
--- /dev/null
+++ b/core/modules/system/tests/modules/services_defaults_test/src/TestInjection2.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Drupal\services_defaults_test;
+
+/**
+ * A service that is injected via default autowiring.
+ */
+class TestInjection2 {
+}
diff --git a/core/modules/system/tests/modules/services_defaults_test/src/TestInjectionInterface.php b/core/modules/system/tests/modules/services_defaults_test/src/TestInjectionInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..45a286c6137f7095d93c24b264543e58cdbb133c
--- /dev/null
+++ b/core/modules/system/tests/modules/services_defaults_test/src/TestInjectionInterface.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Drupal\services_defaults_test;
+
+/**
+ * An interface for a service that is injected via default autowiring.
+ */
+interface TestInjectionInterface {
+}
diff --git a/core/modules/system/tests/modules/services_defaults_test/src/TestPrivateService.php b/core/modules/system/tests/modules/services_defaults_test/src/TestPrivateService.php
new file mode 100644
index 0000000000000000000000000000000000000000..3b6e98a113256fa1d12106e89536d4ddefe126ff
--- /dev/null
+++ b/core/modules/system/tests/modules/services_defaults_test/src/TestPrivateService.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Drupal\services_defaults_test;
+
+/**
+ * A service to test private flag.
+ */
+class TestPrivateService {
+}
diff --git a/core/modules/system/tests/modules/services_defaults_test/src/TestService.php b/core/modules/system/tests/modules/services_defaults_test/src/TestService.php
new file mode 100644
index 0000000000000000000000000000000000000000..944a60bd5cfa83e2812ba6eb1e3f473194c7ae2f
--- /dev/null
+++ b/core/modules/system/tests/modules/services_defaults_test/src/TestService.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace Drupal\services_defaults_test;
+
+/**
+ * An autowired service to test _defaults.
+ */
+class TestService {
+
+  /**
+   * @var \Drupal\services_defaults_test\TestInjectionInterface
+   */
+  protected $testInjection;
+
+  /**
+   * @var \Drupal\services_defaults_test\TestInjection2
+   */
+  protected $testInjection2;
+
+  public function __construct(TestInjectionInterface $test_injection, TestInjection2 $test_injection2) {
+    $this->testInjection = $test_injection;
+    $this->testInjection2 = $test_injection2;
+  }
+
+  public function getTestInjection() {
+    return $this->testInjection;
+  }
+
+  public function getTestInjection2() {
+    return $this->testInjection2;
+  }
+
+}
diff --git a/core/tests/Drupal/KernelTests/Core/DependencyInjection/ServicesDefaultsTest.php b/core/tests/Drupal/KernelTests/Core/DependencyInjection/ServicesDefaultsTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..8fdb9e2ffeb87e74383011cab96f664bdd81b3a0
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Core/DependencyInjection/ServicesDefaultsTest.php
@@ -0,0 +1,83 @@
+<?php
+
+namespace Drupal\KernelTests\Core\DependencyInjection;
+
+use Drupal\KernelTests\KernelTestBase;
+use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
+
+/**
+ * Tests services _defaults definition.
+ *
+ * @group DependencyInjection
+ */
+class ServicesDefaultsTest extends KernelTestBase {
+
+  protected static $modules = ['services_defaults_test'];
+
+  /**
+   * Tests that 'services_defaults_test.service' has its dependencies injected.
+   */
+  public function testAutowiring() {
+    // Ensure interface autowiring works.
+    $this->assertSame(
+      $this->container->get('Drupal\services_defaults_test\TestInjection'),
+      $this->container->get('Drupal\services_defaults_test\TestService')->getTestInjection()
+    );
+    // Ensure defaults autowire works.
+    $this->assertSame(
+      $this->container->get('Drupal\services_defaults_test\TestInjection2'),
+      $this->container->get('Drupal\services_defaults_test\TestService')->getTestInjection2()
+    );
+
+    // Ensure that disabling autowiring works.
+    $this->assertNotSame(
+      $this->container->get('Drupal\services_defaults_test\TestInjection'),
+      $this->container->get('services_default_test.no_autowire')->getTestInjection()
+    );
+    $this->assertSame(
+      $this->container->get('services_default_test.no_autowire.arg'),
+      $this->container->get('services_default_test.no_autowire')->getTestInjection()
+    );
+    $this->assertSame(
+      $this->container->get('Drupal\services_defaults_test\TestInjection2'),
+      $this->container->get('services_default_test.no_autowire')->getTestInjection2()
+    );
+
+  }
+
+  /**
+   * Tests that default tags for 'services_defaults_test.service' are applied.
+   */
+  public function testDefaultTags() {
+    // Ensure default tags work.
+    $testServiceDefinition = $this->container->getDefinition('Drupal\services_defaults_test\TestService');
+    $testInjection1Definition = $this->container->getDefinition('Drupal\services_defaults_test\TestInjection');
+    $testInjection2Definition = $this->container->getDefinition('Drupal\services_defaults_test\TestInjection2');
+
+    $this->assertTrue($testServiceDefinition->hasTag('foo.tag1'));
+    $this->assertTrue($testServiceDefinition->hasTag('bar.tag2'));
+    $this->assertSame([['test' => 123]], $testServiceDefinition->getTag('bar.tag2'));
+    $this->assertTrue($testServiceDefinition->hasTag('bar.tag3'));
+    $this->assertSame([['value' => NULL]], $testServiceDefinition->getTag('bar.tag3'));
+
+    $this->assertSame($testServiceDefinition->getTags(), $testInjection1Definition->getTags());
+
+    // Ensure overridden tag works.
+    $this->assertTrue($testInjection2Definition->hasTag('zee.bang'));
+    $this->assertTrue($testInjection2Definition->hasTag('foo.tag1'));
+    $this->assertTrue($testInjection2Definition->hasTag('bar.tag2'));
+    $this->assertSame([['test' => 321], ['test' => 123]], $testInjection2Definition->getTag('bar.tag2'));
+    $this->assertTrue($testInjection2Definition->hasTag('bar.tag3'));
+    $this->assertSame([['value' => NULL]], $testInjection2Definition->getTag('bar.tag3'));
+  }
+
+  /**
+   * Tests that service from 'services_defaults_test.service' is private.
+   */
+  public function testPrivateServices() {
+    // Ensure default and overridden public flag works.
+    $this->expectException(ServiceNotFoundException::class);
+    $this->container->getDefinition('Drupal\services_defaults_test\TestPrivateService');
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/DependencyInjection/YamlFileLoaderTest.php b/core/tests/Drupal/Tests/Core/DependencyInjection/YamlFileLoaderTest.php
index 52728239ee84731ceb3c5f53a249e8e028803924..7354d304c3d959a3078c840ec0a2879ce983c8d7 100644
--- a/core/tests/Drupal/Tests/Core/DependencyInjection/YamlFileLoaderTest.php
+++ b/core/tests/Drupal/Tests/Core/DependencyInjection/YamlFileLoaderTest.php
@@ -58,4 +58,118 @@ class: \Drupal\Core\ExampleClass
     $this->assertSame('Drupal\Core\ExampleClass', $builder->getDefinition('Drupal\Core\ExampleClass')->getClass());
   }
 
+  /**
+   * @dataProvider providerTestExceptions
+   */
+  public function testExceptions($yml, $message) {
+    vfsStream::setup('drupal', NULL, [
+      'modules' => [
+        'example' => [
+          'example.yml' => $yml,
+        ],
+      ],
+    ]);
+
+    $builder = new ContainerBuilder();
+    $yaml_file_loader = new YamlFileLoader($builder);
+
+    $this->expectException(\InvalidArgumentException::class);
+    $this->expectExceptionMessage($message);
+    $yaml_file_loader->load('vfs://drupal/modules/example/example.yml');
+  }
+
+  public function providerTestExceptions() {
+    return [
+      '_defaults must be an array' => [<<<YAML
+services:
+  _defaults: string
+YAML,
+        'Service "_defaults" key must be an array, "string" given in "vfs://drupal/modules/example/example.yml".',
+      ],
+      'invalid _defaults key' => [<<<YAML
+services:
+  _defaults:
+    invalid: string
+YAML,
+        'The configuration key "invalid" cannot be used to define a default value in "vfs://drupal/modules/example/example.yml". Allowed keys are "public", "tags", "autowire".',
+      ],
+      'default tags must be an array' => [<<<YAML
+services:
+  _defaults:
+    tags: string
+YAML,
+        'Parameter "tags" in "_defaults" must be an array in "vfs://drupal/modules/example/example.yml". Check your YAML syntax.',
+      ],
+      'default tags must have a name' => [<<<YAML
+services:
+  _defaults:
+    tags:
+      - {}
+YAML,
+        'A "tags" entry in "_defaults" is missing a "name" key in "vfs://drupal/modules/example/example.yml".',
+      ],
+      'default tag name must not be empty' => [<<<YAML
+services:
+  _defaults:
+    tags:
+      - ''
+YAML,
+        'The tag name in "_defaults" must be a non-empty string in "vfs://drupal/modules/example/example.yml".',
+      ],
+      'default tag name must be a string' => [<<<YAML
+services:
+  _defaults:
+    tags:
+      - 123
+YAML,
+        'The tag name in "_defaults" must be a non-empty string in "vfs://drupal/modules/example/example.yml".',
+      ],
+      'default tag attribute must be scalar' => [<<<YAML
+services:
+  _defaults:
+    tags:
+      - { name: tag, value: [] }
+YAML,
+        'Tag "tag", attribute "value" in "_defaults" must be of a scalar-type in "vfs://drupal/modules/example/example.yml". Check your YAML syntax.',
+      ],
+      'tags must be an array' => [<<<YAML
+services:
+  service:
+    tags: string
+YAML,
+        'Parameter "tags" must be an array for service "service" in "vfs://drupal/modules/example/example.yml". Check your YAML syntax.',
+      ],
+      'tags must have a name' => [<<<YAML
+services:
+  service:
+    tags:
+      - {}
+YAML,
+        'A "tags" entry is missing a "name" key for service "service" in "vfs://drupal/modules/example/example.yml".',
+      ],
+      'tag name must not be empty' => [<<<YAML
+services:
+  service:
+    tags:
+      - ''
+YAML,
+        'The tag name for service "service" in "vfs://drupal/modules/example/example.yml" must be a non-empty string.',
+      ],
+      'tag attribute must be scalar' => [<<<YAML
+services:
+  service:
+    tags:
+      - { name: tag, value: [] }
+YAML,
+        'A "tags" attribute must be of a scalar-type for service "service", tag "tag", attribute "value" in "vfs://drupal/modules/example/example.yml". Check your YAML syntax.',
+      ],
+      'service must be array or @service' => [<<<YAML
+services:
+  service: string
+YAML,
+        'A service definition must be an array or a string starting with "@" but string found for service "service" in vfs://drupal/modules/example/example.yml. Check your YAML syntax.',
+      ],
+    ];
+  }
+
 }