diff --git a/django/conf/__init__.py b/django/conf/__init__.py
index 3337fd6f13..5731fe912c 100644
--- a/django/conf/__init__.py
+++ b/django/conf/__init__.py
@@ -9,23 +9,14 @@ for a list of all possible variables.
 import importlib
 import os
 import time
-import traceback
-import warnings
 from pathlib import Path
 
-import django
 from django.conf import global_settings
 from django.core.exceptions import ImproperlyConfigured
-from django.utils.deprecation import RemovedInDjango31Warning
 from django.utils.functional import LazyObject, empty
 
 ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE"
 
-FILE_CHARSET_DEPRECATED_MSG = (
-    'The FILE_CHARSET setting is deprecated. Starting with Django 3.1, all '
-    'files read from disk must be UTF-8 encoded.'
-)
-
 
 class SettingsReference(str):
     """
@@ -114,20 +105,6 @@ class LazySettings(LazyObject):
         """Return True if the settings have already been configured."""
         return self._wrapped is not empty
 
-    @property
-    def FILE_CHARSET(self):
-        stack = traceback.extract_stack()
-        # Show a warning if the setting is used outside of Django.
-        # Stack index: -1 this line, -2 the caller.
-        filename, _line_number, _function_name, _text = stack[-2]
-        if not filename.startswith(os.path.dirname(django.__file__)):
-            warnings.warn(
-                FILE_CHARSET_DEPRECATED_MSG,
-                RemovedInDjango31Warning,
-                stacklevel=2,
-            )
-        return self.__getattr__('FILE_CHARSET')
-
 
 class Settings:
     def __init__(self, settings_module):
@@ -160,9 +137,6 @@ class Settings:
         if not self.SECRET_KEY:
             raise ImproperlyConfigured("The SECRET_KEY setting must not be empty.")
 
-        if self.is_overridden('FILE_CHARSET'):
-            warnings.warn(FILE_CHARSET_DEPRECATED_MSG, RemovedInDjango31Warning)
-
         if hasattr(time, 'tzset') and self.TIME_ZONE:
             # When we can, attempt to validate the timezone. If we can't find
             # this file, no check happens and it's harmless.
@@ -206,8 +180,6 @@ class UserSettingsHolder:
 
     def __setattr__(self, name, value):
         self._deleted.discard(name)
-        if name == 'FILE_CHARSET':
-            warnings.warn(FILE_CHARSET_DEPRECATED_MSG, RemovedInDjango31Warning)
         super().__setattr__(name, value)
 
     def __delattr__(self, name):
diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
index 2274ea3f4f..398696530a 100644
--- a/django/conf/global_settings.py
+++ b/django/conf/global_settings.py
@@ -171,9 +171,6 @@ MANAGERS = ADMINS
 # manually specified. It's used to construct the Content-Type header.
 DEFAULT_CHARSET = 'utf-8'
 
-# Encoding of files read from disk (template and initial SQL files).
-FILE_CHARSET = 'utf-8'
-
 # Email address that error messages come from.
 SERVER_EMAIL = 'root@localhost'
 
diff --git a/django/contrib/staticfiles/storage.py b/django/contrib/staticfiles/storage.py
index 607c11b7d6..69af9ca297 100644
--- a/django/contrib/staticfiles/storage.py
+++ b/django/contrib/staticfiles/storage.py
@@ -285,7 +285,7 @@ class HashedFilesMixin:
                 # ..to apply each replacement pattern to the content
                 if name in adjustable_paths:
                     old_hashed_name = hashed_name
-                    content = original_file.read().decode(settings.FILE_CHARSET)
+                    content = original_file.read().decode('utf-8')
                     for extension, patterns in self._patterns.items():
                         if matches_patterns(path, (extension,)):
                             for pattern, template in patterns:
diff --git a/django/core/management/commands/makemessages.py b/django/core/management/commands/makemessages.py
index 6c1e8248ce..69d7c41a2b 100644
--- a/django/core/management/commands/makemessages.py
+++ b/django/core/management/commands/makemessages.py
@@ -102,8 +102,7 @@ class BuildFile:
         if not self.is_templatized:
             return
 
-        encoding = settings.FILE_CHARSET if self.command.settings_available else 'utf-8'
-        with open(self.path, encoding=encoding) as fp:
+        with open(self.path, encoding='utf-8') as fp:
             src_data = fp.read()
 
         if self.domain == 'djangojs':
diff --git a/django/template/backends/django.py b/django/template/backends/django.py
index 91bfbcf0bb..80d11d3cdd 100644
--- a/django/template/backends/django.py
+++ b/django/template/backends/django.py
@@ -20,7 +20,7 @@ class DjangoTemplates(BaseEngine):
         options = params.pop('OPTIONS').copy()
         options.setdefault('autoescape', True)
         options.setdefault('debug', settings.DEBUG)
-        options.setdefault('file_charset', settings.FILE_CHARSET)
+        options.setdefault('file_charset', 'utf-8')
         libraries = options.get('libraries', {})
         options['libraries'] = self.get_templatetag_libraries(libraries)
         super().__init__(params)
diff --git a/django/template/backends/dummy.py b/django/template/backends/dummy.py
index a59e10d178..6be05ca614 100644
--- a/django/template/backends/dummy.py
+++ b/django/template/backends/dummy.py
@@ -1,6 +1,5 @@
 import string
 
-from django.conf import settings
 from django.core.exceptions import ImproperlyConfigured
 from django.template import Origin, TemplateDoesNotExist
 from django.utils.html import conditional_escape
@@ -28,7 +27,7 @@ class TemplateStrings(BaseEngine):
         tried = []
         for template_file in self.iter_template_filenames(template_name):
             try:
-                with open(template_file, encoding=settings.FILE_CHARSET) as fp:
+                with open(template_file, encoding='utf-8') as fp:
                     template_code = fp.read()
             except FileNotFoundError:
                 tried.append((
diff --git a/django/test/signals.py b/django/test/signals.py
index 31a5017602..68e6ad3fb6 100644
--- a/django/test/signals.py
+++ b/django/test/signals.py
@@ -86,7 +86,6 @@ def reset_template_engines(**kwargs):
     if kwargs['setting'] in {
         'TEMPLATES',
         'DEBUG',
-        'FILE_CHARSET',
         'INSTALLED_APPS',
     }:
         from django.template import engines
diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
index 622baa8376..d0b66502ba 100644
--- a/docs/ref/settings.txt
+++ b/docs/ref/settings.txt
@@ -1414,21 +1414,6 @@ Default: ``None``
 Specifies a timeout in seconds for blocking operations like the connection
 attempt.
 
-.. setting:: FILE_CHARSET
-
-``FILE_CHARSET``
-----------------
-
-Default: ``'utf-8'``
-
-The character encoding used to decode any files read from disk. This includes
-template files, static files, and translation catalogs.
-
-.. deprecated:: 2.2
-
-    This setting is deprecated. Starting with Django 3.1, files read from disk
-    must be UTF-8 encoded.
-
 .. setting:: FILE_UPLOAD_HANDLERS
 
 ``FILE_UPLOAD_HANDLERS``
diff --git a/docs/releases/3.1.txt b/docs/releases/3.1.txt
index 8fb764115e..fdba88f035 100644
--- a/docs/releases/3.1.txt
+++ b/docs/releases/3.1.txt
@@ -232,3 +232,5 @@ to remove usage of these features.
 
 * ``django.contrib.postgres.fields.FloatRangeField`` and
   ``django.contrib.postgres.forms.FloatRangeField`` are removed.
+
+* The ``FILE_CHARSET`` setting is removed.
diff --git a/tests/settings_tests/test_file_charset.py b/tests/settings_tests/test_file_charset.py
deleted file mode 100644
index 1be96a26d2..0000000000
--- a/tests/settings_tests/test_file_charset.py
+++ /dev/null
@@ -1,40 +0,0 @@
-import sys
-from types import ModuleType
-
-from django.conf import FILE_CHARSET_DEPRECATED_MSG, Settings, settings
-from django.test import SimpleTestCase, ignore_warnings
-from django.utils.deprecation import RemovedInDjango31Warning
-
-
-class DeprecationTests(SimpleTestCase):
-    msg = FILE_CHARSET_DEPRECATED_MSG
-
-    def test_override_settings_warning(self):
-        with self.assertRaisesMessage(RemovedInDjango31Warning, self.msg):
-            with self.settings(FILE_CHARSET='latin1'):
-                pass
-
-    def test_settings_init_warning(self):
-        settings_module = ModuleType('fake_settings_module')
-        settings_module.FILE_CHARSET = 'latin1'
-        settings_module.SECRET_KEY = 'ABC'
-        sys.modules['fake_settings_module'] = settings_module
-        try:
-            with self.assertRaisesMessage(RemovedInDjango31Warning, self.msg):
-                Settings('fake_settings_module')
-        finally:
-            del sys.modules['fake_settings_module']
-
-    def test_access_warning(self):
-        with self.assertRaisesMessage(RemovedInDjango31Warning, self.msg):
-            settings.FILE_CHARSET
-        # Works a second time.
-        with self.assertRaisesMessage(RemovedInDjango31Warning, self.msg):
-            settings.FILE_CHARSET
-
-    @ignore_warnings(category=RemovedInDjango31Warning)
-    def test_access(self):
-        with self.settings(FILE_CHARSET='latin1'):
-            self.assertEqual(settings.FILE_CHARSET, 'latin1')
-            # Works a second time.
-            self.assertEqual(settings.FILE_CHARSET, 'latin1')