diff --git a/doc/source/whatsnew/v2.1.0.rst b/doc/source/whatsnew/v2.1.0.rst
index 55f396c621a99..7c7b13ab4e89d 100644
--- a/doc/source/whatsnew/v2.1.0.rst
+++ b/doc/source/whatsnew/v2.1.0.rst
@@ -264,12 +264,12 @@ Deprecations
 - Deprecated unused "closed" and "normalize" keywords in the :class:`DatetimeIndex` constructor (:issue:`52628`)
 - Deprecated unused "closed" keyword in the :class:`TimedeltaIndex` constructor (:issue:`52628`)
 - Deprecated logical operation between two non boolean :class:`Series` with different indexes always coercing the result to bool dtype. In a future version, this will maintain the return type of the inputs. (:issue:`52500`, :issue:`52538`)
+- Deprecated :meth:`Series.first` and :meth:`DataFrame.first` (please create a mask and filter using ``.loc`` instead) (:issue:`45908`)
 - Deprecated allowing ``downcast`` keyword other than ``None``, ``False``, "infer", or a dict with these as values in :meth:`Series.fillna`, :meth:`DataFrame.fillna` (:issue:`40988`)
 - Deprecated allowing arbitrary ``fill_value`` in :class:`SparseDtype`, in a future version the ``fill_value`` will need to be compatible with the ``dtype.subtype``, either a scalar that can be held by that subtype or ``NaN`` for integer or bool subtypes (:issue:`23124`)
 - Deprecated behavior of :func:`assert_series_equal` and :func:`assert_frame_equal` considering NA-like values (e.g. ``NaN`` vs ``None`` as equivalent) (:issue:`52081`)
 - Deprecated constructing :class:`SparseArray` from scalar data, pass a sequence instead (:issue:`53039`)
 - Deprecated positional indexing on :class:`Series` with :meth:`Series.__getitem__` and :meth:`Series.__setitem__`, in a future version ``ser[item]`` will *always* interpret ``item`` as a label, not a position (:issue:`50617`)
--
 
 .. ---------------------------------------------------------------------------
 .. _whatsnew_210.performance:
diff --git a/pandas/conftest.py b/pandas/conftest.py
index 077939d2d05ce..7dab1714e0aa3 100644
--- a/pandas/conftest.py
+++ b/pandas/conftest.py
@@ -145,6 +145,11 @@ def pytest_collection_modifyitems(items, config) -> None:
             "(Series|DataFrame).bool is now deprecated and will be removed "
             "in future version of pandas",
         ),
+        (
+            "pandas.core.generic.NDFrame.first",
+            "first is deprecated and will be removed in a future version. "
+            "Please create a mask and filter using `.loc` instead",
+        ),
     ]
 
     for item in items:
diff --git a/pandas/core/generic.py b/pandas/core/generic.py
index bcfbfa1a2b713..1adc331e3cd50 100644
--- a/pandas/core/generic.py
+++ b/pandas/core/generic.py
@@ -9162,6 +9162,12 @@ def first(self, offset) -> Self:
         3 days observed in the dataset, and therefore data for 2018-04-13 was
         not returned.
         """
+        warnings.warn(
+            "first is deprecated and will be removed in a future version. "
+            "Please create a mask and filter using `.loc` instead",
+            FutureWarning,
+            stacklevel=find_stack_level(),
+        )
         if not isinstance(self.index, DatetimeIndex):
             raise TypeError("'first' only supports a DatetimeIndex index")
 
diff --git a/pandas/tests/frame/methods/test_first_and_last.py b/pandas/tests/frame/methods/test_first_and_last.py
index 64f6665ecd709..18173f7c66198 100644
--- a/pandas/tests/frame/methods/test_first_and_last.py
+++ b/pandas/tests/frame/methods/test_first_and_last.py
@@ -10,29 +10,36 @@
 )
 import pandas._testing as tm
 
+deprecated_msg = "first is deprecated"
+
 
 class TestFirst:
     def test_first_subset(self, frame_or_series):
         ts = tm.makeTimeDataFrame(freq="12h")
         ts = tm.get_obj(ts, frame_or_series)
-        result = ts.first("10d")
-        assert len(result) == 20
+        with tm.assert_produces_warning(FutureWarning, match=deprecated_msg):
+            result = ts.first("10d")
+            assert len(result) == 20
 
         ts = tm.makeTimeDataFrame(freq="D")
         ts = tm.get_obj(ts, frame_or_series)
-        result = ts.first("10d")
-        assert len(result) == 10
+        with tm.assert_produces_warning(FutureWarning, match=deprecated_msg):
+            result = ts.first("10d")
+            assert len(result) == 10
 
-        result = ts.first("3M")
-        expected = ts[:"3/31/2000"]
-        tm.assert_equal(result, expected)
+        with tm.assert_produces_warning(FutureWarning, match=deprecated_msg):
+            result = ts.first("3M")
+            expected = ts[:"3/31/2000"]
+            tm.assert_equal(result, expected)
 
-        result = ts.first("21D")
-        expected = ts[:21]
-        tm.assert_equal(result, expected)
+        with tm.assert_produces_warning(FutureWarning, match=deprecated_msg):
+            result = ts.first("21D")
+            expected = ts[:21]
+            tm.assert_equal(result, expected)
 
-        result = ts[:0].first("3M")
-        tm.assert_equal(result, ts[:0])
+        with tm.assert_produces_warning(FutureWarning, match=deprecated_msg):
+            result = ts[:0].first("3M")
+            tm.assert_equal(result, ts[:0])
 
     def test_first_last_raises(self, frame_or_series):
         # GH#20725
@@ -40,7 +47,11 @@ def test_first_last_raises(self, frame_or_series):
         obj = tm.get_obj(obj, frame_or_series)
 
         msg = "'first' only supports a DatetimeIndex index"
-        with pytest.raises(TypeError, match=msg):  # index is not a DatetimeIndex
+        with tm.assert_produces_warning(
+            FutureWarning, match=deprecated_msg
+        ), pytest.raises(
+            TypeError, match=msg
+        ):  # index is not a DatetimeIndex
             obj.first("1D")
 
         msg = "'last' only supports a DatetimeIndex index"
@@ -73,7 +84,8 @@ def test_last_subset(self, frame_or_series):
     def test_first_with_first_day_last_of_month(self, frame_or_series, start, periods):
         # GH#29623
         x = frame_or_series([1] * 100, index=bdate_range(start, periods=100))
-        result = x.first("1M")
+        with tm.assert_produces_warning(FutureWarning, match=deprecated_msg):
+            result = x.first("1M")
         expected = frame_or_series(
             [1] * periods, index=bdate_range(start, periods=periods)
         )
@@ -82,16 +94,20 @@ def test_first_with_first_day_last_of_month(self, frame_or_series, start, period
     def test_first_with_first_day_end_of_frq_n_greater_one(self, frame_or_series):
         # GH#29623
         x = frame_or_series([1] * 100, index=bdate_range("2010-03-31", periods=100))
-        result = x.first("2M")
+        with tm.assert_produces_warning(FutureWarning, match=deprecated_msg):
+            result = x.first("2M")
         expected = frame_or_series(
             [1] * 23, index=bdate_range("2010-03-31", "2010-04-30")
         )
         tm.assert_equal(result, expected)
 
-    @pytest.mark.parametrize("func", ["first", "last"])
-    def test_empty_not_input(self, func):
+    def test_empty_not_input(self):
         # GH#51032
         df = DataFrame(index=pd.DatetimeIndex([]))
-        result = getattr(df, func)(offset=1)
+        result = df.last(offset=1)
+
+        with tm.assert_produces_warning(FutureWarning, match=deprecated_msg):
+            result = df.first(offset=1)
+
         tm.assert_frame_equal(df, result)
         assert df is not result
diff --git a/pandas/tests/generic/test_finalize.py b/pandas/tests/generic/test_finalize.py
index 8159024966b0f..9dfa2c8a5a90a 100644
--- a/pandas/tests/generic/test_finalize.py
+++ b/pandas/tests/generic/test_finalize.py
@@ -8,6 +8,7 @@
 import pytest
 
 import pandas as pd
+import pandas._testing as tm
 
 # TODO:
 # * Binary methods (mul, div, etc.)
@@ -333,16 +334,6 @@
         ({"A": [1, 1, 1, 1]}, pd.date_range("2000", periods=4)),
         operator.methodcaller("between_time", "12:00", "13:00"),
     ),
-    (
-        pd.Series,
-        (1, pd.date_range("2000", periods=4)),
-        operator.methodcaller("first", "3D"),
-    ),
-    (
-        pd.DataFrame,
-        ({"A": [1, 1, 1, 1]}, pd.date_range("2000", periods=4)),
-        operator.methodcaller("first", "3D"),
-    ),
     (
         pd.Series,
         (1, pd.date_range("2000", periods=4)),
@@ -451,6 +442,22 @@ def test_finalize_called(ndframe_method):
     assert result.attrs == {"a": 1}
 
 
+@pytest.mark.parametrize(
+    "data",
+    [
+        pd.Series(1, pd.date_range("2000", periods=4)),
+        pd.DataFrame({"A": [1, 1, 1, 1]}, pd.date_range("2000", periods=4)),
+    ],
+)
+def test_finalize_first(data):
+    deprecated_msg = "first is deprecated"
+
+    data.attrs = {"a": 1}
+    with tm.assert_produces_warning(FutureWarning, match=deprecated_msg):
+        result = data.first("3D")
+        assert result.attrs == {"a": 1}
+
+
 @not_implemented_mark
 def test_finalize_called_eval_numexpr():
     pytest.importorskip("numexpr")