diff --git a/doc/changes.rst b/doc/changes.rst
index c3f84c4fc..906f99025 100644
--- a/doc/changes.rst
+++ b/doc/changes.rst
@@ -7,8 +7,11 @@ desispec Change Log
 
 * Fix installation when using desiutil/3.5.0, dropping support of
   `python setup.py test` as a side-effect [PR `#2437`_]
+* `$DESI_SPECTRO_REDUX` default to `$DESI_ROOT/spectro/redux` for
+  `desispec.io.findfile` [PR `#2448`_]
 
 .. _`#2437`: https://github.com/desihub/desispec/pull/2437
+.. _`#2448`: https://github.com/desihub/desispec/pull/2448
 
 0.68.1 (2024-11-08)
 -------------------
diff --git a/py/desispec/io/meta.py b/py/desispec/io/meta.py
index 284d334bb..9e37d8768 100755
--- a/py/desispec/io/meta.py
+++ b/py/desispec/io/meta.py
@@ -782,7 +782,10 @@ def rawdata_root():
     Raises:
         KeyError: if these environment variables aren't set.
     """
-    return os.environ['DESI_SPECTRO_DATA']
+    if 'DESI_SPECTRO_DATA' in os.environ:
+        return os.environ['DESI_SPECTRO_DATA']
+    else:
+        return os.path.join(os.environ['DESI_ROOT'], 'spectro', 'data')
 
 
 def specprod_root(specprod=None, readonly=False):
@@ -808,7 +811,10 @@ def specprod_root(specprod=None, readonly=False):
         specprod = os.environ['SPECPROD']
 
     if '/' not in specprod:
-        specprod = os.path.join(os.environ['DESI_SPECTRO_REDUX'], specprod)
+        if 'DESI_SPECTRO_REDUX' in os.environ:
+            specprod = os.path.join(os.environ['DESI_SPECTRO_REDUX'], specprod)
+        else:
+            specprod = os.path.join(os.environ['DESI_ROOT'], 'spectro', 'redux', specprod)
 
     if readonly:
         specprod = get_readonly_filepath(specprod)
diff --git a/py/desispec/skygradpca.py b/py/desispec/skygradpca.py
index 0a1be97d6..57dc0aecc 100644
--- a/py/desispec/skygradpca.py
+++ b/py/desispec/skygradpca.py
@@ -248,9 +248,7 @@ def make_all_pcs(specprod, minnight=20200101,
         dictionary[camera, petal] containing the corresponding SkyGradPCA
         object.
     """
-    exps = Table.read(os.path.join(
-        os.environ['DESI_SPECTRO_REDUX'], specprod,
-        f'exposures-{specprod}.csv'))
+    exps = Table.read(desispec.io.findfile('exposures_csv', specprod=specprod))
     m = ((exps['NIGHT'] >= minnight) &
          (exps['SKY_MAG_R_SPEC'] < 19.5) &
          (exps['EXPTIME'] > 60) &
@@ -261,10 +259,8 @@ def make_all_pcs(specprod, minnight=20200101,
     combos = [[c, p] for c in cameras for p in petals]
     fnall = []
     for c, p in combos:
-        fn = [os.path.join(os.environ['DESI_SPECTRO_REDUX'],
-                           specprod, 'exposures', f'{e["NIGHT"]:08d}',
-                           f'{e["EXPID"]:08d}',
-                           f'sframe-{c}{p}-{e["EXPID"]:08d}.fits')
+        fn = [desispec.io.findfile('sframe', night=e["NIGHT"], expid=e["EXPID"],
+                                   camera=f'{c}{p}', specprod=specprod)
               for e in exps]
         fnall.append(fn)
 
diff --git a/py/desispec/test/test_io.py b/py/desispec/test/test_io.py
index 4bbf383b7..13ee963c3 100644
--- a/py/desispec/test/test_io.py
+++ b/py/desispec/test/test_io.py
@@ -828,20 +828,24 @@ def test_findfile(self):
         the_exception = cm.exception
         self.assertTrue(str(the_exception), "Missing inputs for")
 
-        #- Some findfile calls require $DESI_SPECTRO_DATA; others do not
+        #- $DESI_SPECTRO_DATA is used if set, but defaults to $DESI_ROOT/spectro/data
+        path1 = findfile('raw', night=20201020, expid=123)
         del os.environ['DESI_SPECTRO_DATA']
-        x = findfile('spectra', night=20201020, tile=20111, spectrograph=2)
-        self.assertTrue(x is not None)
-        with self.assertRaises(KeyError):
-            x = findfile('raw', night='20150101', expid=123)
+        path2 = findfile('raw', night=20201020, expid=123)
+        self.assertEqual(path1, path2)
+        os.environ['DESI_SPECTRO_DATA'] = '/blat/foo'
+        path3 = findfile('raw', night=20201020, expid=123)
+        self.assertEqual(path3, path1.replace(os.path.expandvars('$DESI_ROOT/spectro/data'), '/blat/foo'))
         os.environ['DESI_SPECTRO_DATA'] = self.testEnv['DESI_SPECTRO_DATA']
 
-        #- Some require $DESI_SPECTRO_REDUX; others to not
+        #- $DESI_SPECTRO_REDUX is used if set, but defaults to $DESI_ROOT/spectro/redux
+        path1 = findfile('spectra', night=20201020, tile=20111, spectrograph=2)
         del os.environ['DESI_SPECTRO_REDUX']
-        x = findfile('raw', night='20150101', expid=123)
-        self.assertTrue(x is not None)
-        with self.assertRaises(KeyError):
-            x = findfile('spectra', night=20201020, tile=20111, spectrograph=2)
+        path2 = findfile('spectra', night=20201020, tile=20111, spectrograph=2)
+        self.assertEqual(path1, path2)
+        os.environ['DESI_SPECTRO_REDUX'] = '/blat/foo'
+        path3 = findfile('spectra', night=20201020, tile=20111, spectrograph=2)
+        self.assertEqual(path3, path1.replace(os.path.expandvars('$DESI_ROOT/spectro/redux'), '/blat/foo'))
         os.environ['DESI_SPECTRO_REDUX'] = self.testEnv['DESI_SPECTRO_REDUX']
 
         #- Camera is case insensitive
@@ -1747,3 +1751,12 @@ def test_specprod_root(self):
         self.assertEqual(specprod_root('blat/foo'), 'blat/foo')
         self.assertEqual(specprod_root('/blat/foo'), '/blat/foo')
 
+        #- $DESI_SPECTRO_REDUX overrides $DESI_ROOT/spectro/redux, but isn't required
+        del os.environ['DESI_SPECTRO_REDUX']
+        self.assertEqual(specprod_root(),
+                         os.path.expandvars('$DESI_ROOT/spectro/redux/$SPECPROD'))
+        os.environ['DESI_SPECTRO_REDUX'] = '/blat/foo'
+        self.assertEqual(specprod_root(),
+                         os.path.expandvars('/blat/foo/$SPECPROD'))
+
+
diff --git a/py/desispec/tpcorrparam.py b/py/desispec/tpcorrparam.py
index 98cb0ab52..e8acd88ea 100644
--- a/py/desispec/tpcorrparam.py
+++ b/py/desispec/tpcorrparam.py
@@ -177,8 +177,7 @@ def gather_dark_tpcorr(specprod=None, nproc=4):
     """
     if specprod is None:
         specprod = os.environ.get('SPECPROD', 'daily')
-    expfn = os.path.join(os.environ['DESI_SPECTRO_REDUX'],
-                         specprod, f'exposures-{specprod}.fits')
+    expfn = desispec.io.findfile('exposures', specprod=specprod)
     exps = fits.getdata(expfn)
     m = ((exps['EXPTIME'] > 300) & (exps['SKY_MAG_R_SPEC'] > 20.5) &
          (exps['SKY_MAG_R_SPEC'] < 30) & (exps['FAPRGRM'] == 'dark'))