diff --git a/indra/llimage/llimage.cpp b/indra/llimage/llimage.cpp
index acfc254b654f9c109ecdcdf05532cf4121969332..16609b60be2e9cb4d35c2ac067519fd9e36ebce6 100644
--- a/indra/llimage/llimage.cpp
+++ b/indra/llimage/llimage.cpp
@@ -990,6 +990,28 @@ void LLImageRaw::verticalFlip()
 }
 
 
+bool LLImageRaw::checkHasTransparentPixels()
+{
+    if (getComponents() != 4)
+    {
+        return false;
+    }
+
+    U8* data = getData();
+    U32 pixels = getWidth() * getHeight();
+
+    // check alpha channel for all 255
+    for (U32 i = 0; i < pixels; ++i)
+    {
+        if (data[i * 4 + 3] != 255)
+        {
+            return true;
+        }
+    }
+
+    return false;
+}
+
 bool LLImageRaw::optimizeAwayAlpha()
 {
     if (getComponents() == 4)
diff --git a/indra/llimage/llimage.h b/indra/llimage/llimage.h
index fc8d62cc9630dbf0dddb823e38cc43c5ef797546..93b58b2356eb55d29e6ae8a07146a58690c893a2 100644
--- a/indra/llimage/llimage.h
+++ b/indra/llimage/llimage.h
@@ -210,6 +210,8 @@ class LLImageRaw : public LLImageBase
 
 	void verticalFlip();
     
+    // Returns true if the image is not fully opaque
+    bool checkHasTransparentPixels();
     // if the alpha channel is all 100% opaque, delete it
     // returns true if alpha channel was deleted
     bool optimizeAwayAlpha();
diff --git a/indra/newview/llfloaterregioninfo.cpp b/indra/newview/llfloaterregioninfo.cpp
index 8c4b6fc85732fcec352ee21a7bbabee08ab53099..706f7cf943df6775a86a391024a780ac897462d8 100644
--- a/indra/newview/llfloaterregioninfo.cpp
+++ b/indra/newview/llfloaterregioninfo.cpp
@@ -1330,7 +1330,7 @@ BOOL LLPanelRegionTerrainInfo::validateTextureSizes()
 		if (!texture_ctrl) continue;
 
 		LLUUID image_asset_id = texture_ctrl->getImageAssetID();
-		LLViewerTexture* img = LLViewerTextureManager::getFetchedTexture(image_asset_id);
+		LLViewerFetchedTexture* img = LLViewerTextureManager::getFetchedTexture(image_asset_id);
 		S32 components = img->getComponents();
 		// Must ask for highest resolution version's width. JC
 		S32 width = img->getFullWidth();
@@ -1348,6 +1348,33 @@ BOOL LLPanelRegionTerrainInfo::validateTextureSizes()
 			return FALSE;
 		}
 
+        if (components == 4)
+        {
+            if (!img->hasSavedRawImage())
+            {
+                // Raw image isn't loaded yet
+                // Assume it's invalid due to presence of alpha channel
+                LLSD args;
+                args["TEXTURE_NUM"] = i+1;
+                args["TEXTURE_BIT_DEPTH"] = llformat("%d",components * 8);
+                LLNotificationsUtil::add("InvalidTerrainAlphaNotFullyLoaded", args);
+                return FALSE;
+            }
+            // Slower path: Calculate alpha from raw image pixels (not needed
+            // for GLTF materials, which use alphaMode to determine
+            // transparency)
+            // Raw image is pretty much guaranteed to be saved due to the texture swatches
+            LLImageRaw* raw = img->getSavedRawImage();
+            if (raw->checkHasTransparentPixels())
+            {
+                LLSD args;
+                args["TEXTURE_NUM"] = i+1;
+                LLNotificationsUtil::add("InvalidTerrainAlpha", args);
+                return FALSE;
+            }
+            LL_WARNS() << "Terrain texture image in slot " << i << " with ID " << image_asset_id << " has alpha channel, but pixels are opaque. Is alpha being optimized away in the texture uploader?" << LL_ENDL;
+        }
+
 		if (width > max_terrain_texture_size || height > max_terrain_texture_size)
 		{
 
diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml
index 1a6cadf43e0482b1ad22f7edef7652612e591f8c..c1e6e7b9f0c15f43146b989b7dfdcfa21b4202d9 100644
--- a/indra/newview/skins/default/xui/en/notifications.xml
+++ b/indra/newview/skins/default/xui/en/notifications.xml
@@ -4008,6 +4008,32 @@ Replace texture [TEXTURE_NUM] with an RGB [MAX_SIZE]x[MAX_SIZE] or smaller image
   <tag>fail</tag>
   </notification>
 
+  <notification
+   icon="alertmodal.tga"
+   name="InvalidTerrainAlphaNotFullyLoaded"
+   type="alertmodal">
+Couldn&apos;t set region textures:
+Terrain texture [TEXTURE_NUM] is not fully loaded, but is assumed to contain transparency due to a bit depth of [TEXTURE_BIT_DEPTH]. Transparency is not currently supported for terrain textures.
+
+If texture [TEXTURE_NUM] is opaque, wait for the texture to fully load and then click &quot;Apply&quot; again.
+
+Alpha is only supported for terrain materials (PBR Metallic Roughness), when alphaMode="MASK" and doubleSided=false.
+  <tag>fail</tag>
+  </notification>
+
+  <notification
+   icon="alertmodal.tga"
+   name="InvalidTerrainAlpha"
+   type="alertmodal">
+Couldn&apos;t set region textures:
+Terrain texture [TEXTURE_NUM] contains transparency. Transparency is not currently supported for terrain textures.
+
+Replace texture [TEXTURE_NUM] with an opaque RGB image, then click &quot;Apply&quot; again.
+
+Alpha is only supported for terrain materials (PBR Metallic Roughness), when alphaMode="MASK" and doubleSided=false.
+  <tag>fail</tag>
+  </notification>
+
   <notification
    icon="alertmodal.tga"
    name="InvalidTerrainSize"