diff --git a/core/modules/file/src/Entity/File.php b/core/modules/file/src/Entity/File.php
index a9ade9e..b4ec092 100644
--- a/core/modules/file/src/Entity/File.php
+++ b/core/modules/file/src/Entity/File.php
@@ -4,9 +4,12 @@
 
 use Drupal\Core\Entity\ContentEntityBase;
 use Drupal\Core\Entity\EntityChangedTrait;
+use Drupal\Core\Entity\EntityMalformedException;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\Exception\UndefinedLinkTemplateException;
 use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\Core\Url;
 use Drupal\file\FileInterface;
 use Drupal\user\UserInterface;
 
@@ -71,12 +74,42 @@ public function setFileUri($uri) {
    * @see file_url_transform_relative()
    */
   public function url($rel = 'canonical', $options = []) {
+    // This method is deprecated on the interface. For file entities it is
+    // especially important not to use url() because the behavior is different
+    // from self::toUrl(). In the case where the URL for a physical file is
+    // needed (as opposed to the canonical URL for the file entity) call
+    // file_create_url($this->getFileUri()) directly.
+    // trigger_error('For file entities, it is important to use toUrl().', E_USER_DEPRECATED);
     return file_create_url($this->getFileUri());
   }
 
   /**
    * {@inheritdoc}
    */
+  public function toUrl($rel = 'canonical', array $options = []) {
+    // Core does not provide any link templates for file entities and the ID is
+    // not needed to generate the URL. This function catches exceptions for both
+    // situations and creates a link if the canonical relationship is requested.
+    try {
+      return parent::toUrl($rel, $options);
+    }
+    catch (UndefinedLinkTemplateException $e) {
+      if ($rel === 'canonical') {
+        return Url::fromUri(file_create_url($this->getFileUri()), $options);
+      }
+      throw $e;
+    }
+    catch (EntityMalformedException $e) {
+      if ($rel === 'canonical') {
+        return Url::fromUri(file_create_url($this->getFileUri()), $options);
+      }
+      throw $e;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function getMimeType() {
     return $this->get('filemime')->value;
   }
diff --git a/core/modules/file/tests/src/Kernel/LinkTest.php b/core/modules/file/tests/src/Kernel/LinkTest.php
new file mode 100644
index 0000000..ff451e1
--- /dev/null
+++ b/core/modules/file/tests/src/Kernel/LinkTest.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace Drupal\Tests\file\Kernel;
+
+use Drupal\Core\Entity\Exception\UndefinedLinkTemplateException;
+use Drupal\Core\Url;
+
+/**
+ * Tests \Drupal\file\Entity\File::link().
+ *
+ * @group file
+ */
+class LinkTest extends FileManagedUnitTestBase {
+
+  /**
+   * Creates a link for a public file and tests the public link.
+   */
+  public function testPublicLink() {
+    $file = $this->createFile('druplicon.txt', NULL, 'public');
+    $this->assertEquals($file->toLink()
+      ->toString(), "<a href=\"{$file->toUrl()->toString()}\">{$file->label()}</a>");
+  }
+
+  /**
+   * Tests toUrl() against a URI callback.
+   */
+  public function testUriCallback() {
+    $file = $this->createFile('druplicon.txt', NULL, 'public');
+    $expected = $file->toUrl()->toString() . '_test';
+    $file->getEntityType()->setUriCallback(function ($fileEntity) {
+      return Url::fromUri(file_create_url($fileEntity->getFileUri()) . '_test');
+    });
+    $actual = $file->toUrl()->toString();
+    $this->assertEquals($expected, $actual);
+  }
+
+  /**
+   * Tests that only canonical links are supported.
+   */
+  public function testNonCanonicalLink() {
+    $this->setExpectedException(UndefinedLinkTemplateException::class);
+
+    $file = $this->createFile('druplicon.txt', NULL, 'public');
+    $file->toLink('', 'notcanonical');
+  }
+
+}
