diff --git a/Marlin/Configuration.h b/Marlin/Configuration.h
index d6a7456ccf..e1f9a4be70 100644
--- a/Marlin/Configuration.h
+++ b/Marlin/Configuration.h
@@ -111,6 +111,13 @@
  */
 //#define SERIAL_PORT_2 -1
 
+/**
+ * Select a third serial port on the board to use for communication with the host.
+ * Currently only supported for AVR, DUE, LPC1768/9 and STM32/STM32F1
+ * :[-1, 0, 1, 2, 3, 4, 5, 6, 7]
+ */
+//#define SERIAL_PORT_3 1
+
 /**
  * This setting determines the communication speed of the printer.
  *
diff --git a/Marlin/src/HAL/AVR/HAL.h b/Marlin/src/HAL/AVR/HAL.h
index 64e4f764dc..e24b923ef0 100644
--- a/Marlin/src/HAL/AVR/HAL.h
+++ b/Marlin/src/HAL/AVR/HAL.h
@@ -103,6 +103,13 @@ typedef int8_t pin_t;
     #endif
     #define MYSERIAL2 customizedSerial2
   #endif
+
+  #ifdef SERIAL_PORT_3
+    #if !WITHIN(SERIAL_PORT_3, -1, 3)
+      #error "SERIAL_PORT_3 must be from 0 to 3, or -1 for USB Serial."
+    #endif
+    #define MYSERIAL3 customizedSerial3
+  #endif
 #endif
 
 #ifdef MMU2_SERIAL_PORT
diff --git a/Marlin/src/HAL/AVR/MarlinSerial.cpp b/Marlin/src/HAL/AVR/MarlinSerial.cpp
index 82cdcfbe83..cd8bf5e690 100644
--- a/Marlin/src/HAL/AVR/MarlinSerial.cpp
+++ b/Marlin/src/HAL/AVR/MarlinSerial.cpp
@@ -585,6 +585,22 @@ MSerialT1 customizedSerial1(MSerialT1::HasEmergencyParser);
 
 #endif // SERIAL_PORT_2
 
+#ifdef SERIAL_PORT_3
+
+  // Hookup ISR handlers
+  ISR(SERIAL_REGNAME(USART, SERIAL_PORT_3, _RX_vect)) {
+    MarlinSerial<MarlinSerialCfg<SERIAL_PORT_3>>::store_rxd_char();
+  }
+
+  ISR(SERIAL_REGNAME(USART, SERIAL_PORT_3, _UDRE_vect)) {
+    MarlinSerial<MarlinSerialCfg<SERIAL_PORT_3>>::_tx_udr_empty_irq();
+  }
+
+  template class MarlinSerial< MarlinSerialCfg<SERIAL_PORT_3> >;
+  MSerialT3 customizedSerial3(MSerialT3::HasEmergencyParser);
+
+#endif // SERIAL_PORT_3
+
 #ifdef MMU2_SERIAL_PORT
 
   ISR(SERIAL_REGNAME(USART, MMU2_SERIAL_PORT, _RX_vect)) {
diff --git a/Marlin/src/HAL/AVR/MarlinSerial.h b/Marlin/src/HAL/AVR/MarlinSerial.h
index 26066d7208..0565c7b9db 100644
--- a/Marlin/src/HAL/AVR/MarlinSerial.h
+++ b/Marlin/src/HAL/AVR/MarlinSerial.h
@@ -246,6 +246,11 @@
     extern MSerialT2 customizedSerial2;
   #endif
 
+  #ifdef SERIAL_PORT_3
+    typedef Serial1Class< MarlinSerial< MarlinSerialCfg<SERIAL_PORT_3> > > MSerialT3;
+    extern MSerialT3 customizedSerial3;
+  #endif
+
 #endif // !USBCON
 
 #ifdef MMU2_SERIAL_PORT
diff --git a/Marlin/src/HAL/DUE/HAL.h b/Marlin/src/HAL/DUE/HAL.h
index 2fb927948c..92e26bcf43 100644
--- a/Marlin/src/HAL/DUE/HAL.h
+++ b/Marlin/src/HAL/DUE/HAL.h
@@ -68,6 +68,16 @@ extern DefaultSerial4 MSerial3;
   #endif
 #endif
 
+#ifdef SERIAL_PORT_3
+  #if SERIAL_PORT_3 == -1 || ENABLED(EMERGENCY_PARSER)
+    #define MYSERIAL3 customizedSerial3
+  #elif WITHIN(SERIAL_PORT_3, 0, 3)
+    #define MYSERIAL3 MSERIAL(SERIAL_PORT_3)
+  #else
+    #error "SERIAL_PORT_3 must be from 0 to 3, or -1 for USB Serial."
+  #endif
+#endif
+
 #ifdef MMU2_SERIAL_PORT
   #if WITHIN(MMU2_SERIAL_PORT, 0, 3)
     #define MMU2_SERIAL MSERIAL(MMU2_SERIAL_PORT)
diff --git a/Marlin/src/HAL/DUE/MarlinSerial.cpp b/Marlin/src/HAL/DUE/MarlinSerial.cpp
index 29d9ab0797..fe62ff5607 100644
--- a/Marlin/src/HAL/DUE/MarlinSerial.cpp
+++ b/Marlin/src/HAL/DUE/MarlinSerial.cpp
@@ -486,4 +486,9 @@ void MarlinSerial<Cfg>::flushTX() {
   MSerialT2 customizedSerial2(MarlinSerialCfg<SERIAL_PORT_2>::EMERGENCYPARSER);
 #endif
 
+#if defined(SERIAL_PORT_3) && SERIAL_PORT_3 >= 0
+  template class MarlinSerial< MarlinSerialCfg<SERIAL_PORT_3> >;
+  MSerialT3 customizedSerial3(MarlinSerialCfg<SERIAL_PORT_3>::EMERGENCYPARSER);
+#endif
+
 #endif // ARDUINO_ARCH_SAM
diff --git a/Marlin/src/HAL/DUE/MarlinSerial.h b/Marlin/src/HAL/DUE/MarlinSerial.h
index 496e54c151..4a62e2834f 100644
--- a/Marlin/src/HAL/DUE/MarlinSerial.h
+++ b/Marlin/src/HAL/DUE/MarlinSerial.h
@@ -149,3 +149,8 @@ struct MarlinSerialCfg {
   typedef Serial1Class< MarlinSerial< MarlinSerialCfg<SERIAL_PORT_2> > > MSerialT2;
   extern MSerialT2 customizedSerial2;
 #endif
+
+#if defined(SERIAL_PORT_3) && SERIAL_PORT_3 >= 0
+  typedef Serial1Class< MarlinSerial< MarlinSerialCfg<SERIAL_PORT_3> > > MSerialT3;
+  extern MSerialT3 customizedSerial3;
+#endif
diff --git a/Marlin/src/HAL/DUE/MarlinSerialUSB.cpp b/Marlin/src/HAL/DUE/MarlinSerialUSB.cpp
index fb5f013255..67c597da80 100644
--- a/Marlin/src/HAL/DUE/MarlinSerialUSB.cpp
+++ b/Marlin/src/HAL/DUE/MarlinSerialUSB.cpp
@@ -134,6 +134,9 @@ size_t MarlinSerialUSB::write(const uint8_t c) {
 #if SERIAL_PORT_2 == -1
   MSerialT2 customizedSerial2(TERN0(EMERGENCY_PARSER, true));
 #endif
+#if SERIAL_PORT_3 == -1
+  MSerialT3 customizedSerial3(TERN0(EMERGENCY_PARSER, true));
+#endif
 
 #endif // HAS_USB_SERIAL
 #endif // ARDUINO_ARCH_SAM
diff --git a/Marlin/src/HAL/DUE/MarlinSerialUSB.h b/Marlin/src/HAL/DUE/MarlinSerialUSB.h
index d19fc60eb6..6da1ef8c08 100644
--- a/Marlin/src/HAL/DUE/MarlinSerialUSB.h
+++ b/Marlin/src/HAL/DUE/MarlinSerialUSB.h
@@ -59,3 +59,7 @@ struct MarlinSerialUSB {
   extern MSerialT2 customizedSerial2;
 #endif
 
+#if SERIAL_PORT_3 == -1
+  typedef Serial1Class<MarlinSerialUSB> MSerialT3;
+  extern MSerialT3 customizedSerial3;
+#endif
diff --git a/Marlin/src/HAL/LPC1768/HAL.h b/Marlin/src/HAL/LPC1768/HAL.h
index bcfa6c412f..85e8933920 100644
--- a/Marlin/src/HAL/LPC1768/HAL.h
+++ b/Marlin/src/HAL/LPC1768/HAL.h
@@ -84,6 +84,16 @@ extern DefaultSerial1 USBSerial;
   #endif
 #endif
 
+#ifdef SERIAL_PORT_3
+  #if SERIAL_PORT_3 == -1
+    #define MYSERIAL3 USBSerial
+  #elif WITHIN(SERIAL_PORT_3, 0, 3)
+    #define MYSERIAL3 MSERIAL(SERIAL_PORT_3)
+  #else
+    #error "SERIAL_PORT_3 must be from 0 to 3. You can also use -1 if the board supports Native USB."
+  #endif
+#endif
+
 #ifdef MMU2_SERIAL_PORT
   #if MMU2_SERIAL_PORT == -1
     #define MMU2_SERIAL USBSerial
diff --git a/Marlin/src/HAL/STM32/HAL.h b/Marlin/src/HAL/STM32/HAL.h
index 8ac9e6bd80..2441c46eab 100644
--- a/Marlin/src/HAL/STM32/HAL.h
+++ b/Marlin/src/HAL/STM32/HAL.h
@@ -68,6 +68,16 @@
   #endif
 #endif
 
+#ifdef SERIAL_PORT_3
+  #if SERIAL_PORT_3 == -1
+    #define MYSERIAL3 MSerial0
+  #elif WITHIN(SERIAL_PORT_3, 1, 6)
+    #define MYSERIAL3 MSERIAL(SERIAL_PORT_3)
+  #else
+    #error "SERIAL_PORT_3 must be from 1 to 6. You can also use -1 if the board supports Native USB."
+  #endif
+#endif
+
 #ifdef MMU2_SERIAL_PORT
   #if MMU2_SERIAL_PORT == -1
     #define MMU2_SERIAL MSerial0
diff --git a/Marlin/src/HAL/STM32F1/HAL.h b/Marlin/src/HAL/STM32F1/HAL.h
index 8b306f48fa..b3d8dc9d0b 100644
--- a/Marlin/src/HAL/STM32F1/HAL.h
+++ b/Marlin/src/HAL/STM32F1/HAL.h
@@ -98,6 +98,17 @@
   #endif
 #endif
 
+#ifdef SERIAL_PORT_3
+  #if SERIAL_PORT_3 == -1
+    #define MYSERIAL3 UsbSerial
+  #elif WITHIN(SERIAL_PORT_3, 1, NUM_UARTS)
+    #define MYSERIAL3 MSERIAL(SERIAL_PORT_3)
+  #else
+    #define MYSERIAL3 MSERIAL(1) // dummy port
+    static_assert(false, "SERIAL_PORT_3 must be from 1 to " STRINGIFY(NUM_UARTS) ". You can also use -1 if the board supports Native USB.")
+  #endif
+#endif
+
 #ifdef MMU2_SERIAL_PORT
   #if MMU2_SERIAL_PORT == -1
     #define MMU2_SERIAL UsbSerial
diff --git a/Marlin/src/MarlinCore.cpp b/Marlin/src/MarlinCore.cpp
index 053256b743..2794b80695 100644
--- a/Marlin/src/MarlinCore.cpp
+++ b/Marlin/src/MarlinCore.cpp
@@ -1075,6 +1075,11 @@ void setup() {
     MYSERIAL2.begin(BAUDRATE);
     serial_connect_timeout = millis() + 1000UL;
     while (!MYSERIAL2.connected() && PENDING(millis(), serial_connect_timeout)) { /*nada*/ }
+    #ifdef SERIAL_PORT_3
+      MYSERIAL3.begin(BAUDRATE);
+      serial_connect_timeout = millis() + 1000UL;
+      while (!MYSERIAL3.connected() && PENDING(millis(), serial_connect_timeout)) { /*nada*/ }
+    #endif
   #endif
   SERIAL_ECHOLNPGM("start");
 
diff --git a/Marlin/src/core/serial.cpp b/Marlin/src/core/serial.cpp
index 8af367c801..28442594ce 100644
--- a/Marlin/src/core/serial.cpp
+++ b/Marlin/src/core/serial.cpp
@@ -44,6 +44,9 @@ PGMSTR(SP_X_LBL, " X:"); PGMSTR(SP_Y_LBL, " Y:"); PGMSTR(SP_Z_LBL, " Z:"); PGMST
 #if ENABLED(MEATPACK_ON_SERIAL_PORT_2)
   SerialLeafT2 mpSerial2(false, _SERIAL_LEAF_2);
 #endif
+#if ENABLED(MEATPACK_ON_SERIAL_PORT_3)
+  SerialLeafT3 mpSerial3(false, _SERIAL_LEAF_3);
+#endif
 
 // Step 2: For multiserial, handle the second serial port as well
 #if HAS_MULTI_SERIAL
@@ -52,7 +55,14 @@ PGMSTR(SP_X_LBL, " X:"); PGMSTR(SP_Y_LBL, " Y:"); PGMSTR(SP_Z_LBL, " Z:"); PGMST
     SerialLeafT2 msSerial2(ethernet.have_telnet_client, MYSERIAL2, false);
   #endif
 
-  SerialOutputT multiSerial(SERIAL_LEAF_1, SERIAL_LEAF_2);
+  #define __S_LEAF(N) ,SERIAL_LEAF_##N
+  #define _S_LEAF(N) __S_LEAF(N)
+
+  SerialOutputT multiSerial( SERIAL_LEAF_1 REPEAT_S(2, INCREMENT(NUM_SERIAL), _S_LEAF) );
+
+  #undef __S_LEAF
+  #undef _S_LEAF
+
 #endif
 
 void serialprintPGM(PGM_P str) {
diff --git a/Marlin/src/core/serial.h b/Marlin/src/core/serial.h
index 2628b3d2e5..4565a7fc87 100644
--- a/Marlin/src/core/serial.h
+++ b/Marlin/src/core/serial.h
@@ -95,6 +95,9 @@ extern uint8_t marlin_debug_flags;
     #define _SERIAL_LEAF_2 MYSERIAL2 // Don't create a useless instance here, directly use the existing instance
   #endif
 
+  // Nothing complicated here
+  #define _SERIAL_LEAF_3 MYSERIAL3
+
   // Hook Meatpack if it's enabled on the second leaf
   #if ENABLED(MEATPACK_ON_SERIAL_PORT_2)
     typedef MeatpackSerial<decltype(_SERIAL_LEAF_2)> SerialLeafT2;
@@ -104,7 +107,23 @@ extern uint8_t marlin_debug_flags;
     #define SERIAL_LEAF_2 _SERIAL_LEAF_2
   #endif
 
-  typedef MultiSerial<decltype(SERIAL_LEAF_1), decltype(SERIAL_LEAF_2), 0> SerialOutputT;
+  // Hook Meatpack if it's enabled on the third leaf
+  #if ENABLED(MEATPACK_ON_SERIAL_PORT_3)
+    typedef MeatpackSerial<decltype(_SERIAL_LEAF_3)> SerialLeafT3;
+    extern SerialLeafT3 mpSerial3;
+    #define SERIAL_LEAF_3 mpSerial3
+  #else
+    #define SERIAL_LEAF_3 _SERIAL_LEAF_3
+  #endif
+
+  #define __S_MULTI(N) decltype(SERIAL_LEAF_##N),
+  #define _S_MULTI(N) __S_MULTI(N)
+
+  typedef MultiSerial< REPEAT_S(1, INCREMENT(NUM_SERIAL), _S_MULTI) 0> SerialOutputT;
+
+  #undef __S_MULTI
+  #undef _S_MULTI
+
   extern SerialOutputT        multiSerial;
   #define SERIAL_IMPL         multiSerial
 #else
diff --git a/Marlin/src/core/serial_hook.h b/Marlin/src/core/serial_hook.h
index 45cdcd35ed..d56cb55a66 100644
--- a/Marlin/src/core/serial_hook.h
+++ b/Marlin/src/core/serial_hook.h
@@ -195,54 +195,71 @@ struct RuntimeSerial : public SerialBase< RuntimeSerial<SerialT> >, public Seria
   RuntimeSerial(const bool e, Args... args) : BaseClassT(e), SerialT(args...), writeHook(0), eofHook(0), userPointer(0) {}
 };
 
-// A class that duplicates its output conditionally to 2 serial interfaces
-template <class Serial0T, class Serial1T, const uint8_t offset = 0, const uint8_t step = 1>
-struct MultiSerial : public SerialBase< MultiSerial<Serial0T, Serial1T, offset, step> > {
-  typedef SerialBase< MultiSerial<Serial0T, Serial1T, offset, step> > BaseClassT;
+#define _S_CLASS(N) class Serial##N##T,
+#define _S_NAME(N) Serial##N##T,
+
+template < REPEAT(NUM_SERIAL, _S_CLASS) const uint8_t offset=0, const uint8_t step=1 >
+struct MultiSerial : public SerialBase< MultiSerial< REPEAT(NUM_SERIAL, _S_NAME) offset, step > > {
+  typedef SerialBase< MultiSerial< REPEAT(NUM_SERIAL, _S_NAME) offset, step > > BaseClassT;
+
+  #undef _S_CLASS
+  #undef _S_NAME
 
   SerialMask portMask;
-  Serial0T & serial0;
-  Serial1T & serial1;
 
-  static constexpr uint8_t Usage         =  ((1 << step) - 1); // A bit mask containing as many bits as step
-  static constexpr uint8_t FirstOutput   = (Usage << offset);
-  static constexpr uint8_t SecondOutput  = (Usage << (offset + step));
-  static constexpr uint8_t Both          = FirstOutput | SecondOutput;
+  #define _S_DECLARE(N) Serial##N##T & serial##N;
+  REPEAT(NUM_SERIAL, _S_DECLARE);
+  #undef _S_DECLARE
+
+  static constexpr uint8_t Usage = _BV(step) - 1; // A bit mask containing 'step' bits
+
+  #define _OUT_PORT(N) (Usage << (offset + (step * N))),
+  static constexpr uint8_t output[] = { REPEAT(NUM_SERIAL, _OUT_PORT) };
+  #undef _OUT_PORT
+
+  #define _OUT_MASK(N) | output[N]
+  static constexpr uint8_t ALL = 0 REPEAT(NUM_SERIAL, _OUT_MASK);
+  #undef _OUT_MASK
 
   NO_INLINE void write(uint8_t c) {
-    if (portMask.enabled(FirstOutput))   serial0.write(c);
-    if (portMask.enabled(SecondOutput))  serial1.write(c);
+    #define _S_WRITE(N) if (portMask.enabled(output[N])) serial##N.write(c);
+    REPEAT(NUM_SERIAL, _S_WRITE);
+    #undef _S_WRITE
   }
   NO_INLINE void msgDone() {
-    if (portMask.enabled(FirstOutput))   serial0.msgDone();
-    if (portMask.enabled(SecondOutput))  serial1.msgDone();
+    #define _S_DONE(N) if (portMask.enabled(output[N])) serial##N.msgDone();
+    REPEAT(NUM_SERIAL, _S_DONE);
+    #undef _S_DONE
   }
   int available(serial_index_t index) {
-    if (index.within(0 + offset, step + offset - 1))
-      return serial0.available(index);
-    else if (index.within(step + offset, 2 * step + offset - 1))
-      return serial1.available(index);
+    uint8_t pos = offset;
+    #define _S_AVAILABLE(N) if (index.within(pos, pos + step - 1)) return serial##N.available(index); else pos += step;
+    REPEAT(NUM_SERIAL, _S_AVAILABLE);
+    #undef _S_AVAILABLE
     return false;
   }
   int read(serial_index_t index) {
-    if (index.within(0 + offset, step + offset - 1))
-      return serial0.read(index);
-    else if (index.within(step + offset, 2 * step + offset - 1))
-      return serial1.read(index);
+    uint8_t pos = offset;
+    #define _S_READ(N) if (index.within(pos, pos + step - 1)) return serial##N.read(index); else pos += step;
+    REPEAT(NUM_SERIAL, _S_READ);
+    #undef _S_READ
     return -1;
   }
   void begin(const long br) {
-    if (portMask.enabled(FirstOutput))   serial0.begin(br);
-    if (portMask.enabled(SecondOutput))  serial1.begin(br);
+    #define _S_BEGIN(N) if (portMask.enabled(output[N])) serial##N.begin(br);
+    REPEAT(NUM_SERIAL, _S_BEGIN);
+    #undef _S_BEGIN
   }
   void end() {
-    if (portMask.enabled(FirstOutput))   serial0.end();
-    if (portMask.enabled(SecondOutput))  serial1.end();
+    #define _S_END(N) if (portMask.enabled(output[N])) serial##N.end();
+    REPEAT(NUM_SERIAL, _S_END);
+    #undef _S_END
   }
   bool connected() {
     bool ret = true;
-    if (portMask.enabled(FirstOutput))   ret = CALL_IF_EXISTS(bool, &serial0, connected);
-    if (portMask.enabled(SecondOutput))  ret = ret && CALL_IF_EXISTS(bool, &serial1, connected);
+    #define _S_CONNECTED(N) if (portMask.enabled(output[N]) && !CALL_IF_EXISTS(bool, &serial##N, connected)) ret = false;
+    REPEAT(NUM_SERIAL, _S_CONNECTED);
+    #undef _S_CONNECTED
     return ret;
   }
 
@@ -250,27 +267,32 @@ struct MultiSerial : public SerialBase< MultiSerial<Serial0T, Serial1T, offset,
   using BaseClassT::read;
 
   // Redirect flush
-  NO_INLINE void flush()      {
-    if (portMask.enabled(FirstOutput))   serial0.flush();
-    if (portMask.enabled(SecondOutput))  serial1.flush();
+  NO_INLINE void flush() {
+    #define _S_FLUSH(N) if (portMask.enabled(output[N])) serial##N.flush();
+    REPEAT(NUM_SERIAL, _S_FLUSH);
+    #undef _S_FLUSH
   }
-  NO_INLINE void flushTX()    {
-    if (portMask.enabled(FirstOutput))   CALL_IF_EXISTS(void, &serial0, flushTX);
-    if (portMask.enabled(SecondOutput))  CALL_IF_EXISTS(void, &serial1, flushTX);
+  NO_INLINE void flushTX() {
+    #define _S_FLUSHTX(N) if (portMask.enabled(output[N])) CALL_IF_EXISTS(void, &serial0, flushTX);
+    REPEAT(NUM_SERIAL, _S_FLUSHTX);
+    #undef _S_FLUSHTX
   }
 
   // Forward feature queries
-  SerialFeature features(serial_index_t index) const  {
-    if (index.within(0 + offset, step + offset - 1))
-      return serial0.features(index);
-    else if (index.within(step + offset, 2 * step + offset - 1))
-      return serial1.features(index);
+  SerialFeature features(serial_index_t index) const {
+    uint8_t pos = offset;
+    #define _S_FEATURES(N) if (index.within(pos, pos + step - 1)) return serial##N.features(index); else pos += step;
+    REPEAT(NUM_SERIAL, _S_FEATURES);
+    #undef _S_FEATURES
     return SerialFeature::None;
   }
 
-  MultiSerial(Serial0T & serial0, Serial1T & serial1, const SerialMask mask = Both, const bool e = false) :
-    BaseClassT(e),
-    portMask(mask), serial0(serial0), serial1(serial1) {}
+  #define _S_REFS(N) Serial##N##T & serial##N,
+  #define _S_INIT(N) ,serial##N (serial##N)
+
+  MultiSerial(REPEAT(NUM_SERIAL, _S_REFS) const SerialMask mask = ALL, const bool e = false)
+    : BaseClassT(e), portMask(mask) REPEAT(NUM_SERIAL, _S_INIT) {}
+
 };
 
 // Build the actual serial object depending on current configuration
@@ -278,4 +300,7 @@ struct MultiSerial : public SerialBase< MultiSerial<Serial0T, Serial1T, offset,
 #define ForwardSerial1Class TERN(SERIAL_RUNTIME_HOOK, RuntimeSerial, ForwardSerial)
 #ifdef HAS_MULTI_SERIAL
   #define Serial2Class ConditionalSerial
+  #if NUM_SERIAL >= 3
+    #define Serial3Class ConditionalSerial
+  #endif
 #endif
diff --git a/Marlin/src/inc/Conditionals_LCD.h b/Marlin/src/inc/Conditionals_LCD.h
index d767000501..afec2c2b44 100644
--- a/Marlin/src/inc/Conditionals_LCD.h
+++ b/Marlin/src/inc/Conditionals_LCD.h
@@ -955,15 +955,19 @@
 // Serial Port Info
 //
 #ifdef SERIAL_PORT_2
-  #define NUM_SERIAL 2
   #define HAS_MULTI_SERIAL 1
+  #ifdef SERIAL_PORT_3
+    #define NUM_SERIAL 3
+  #else
+    #define NUM_SERIAL 2
+  #endif
 #elif defined(SERIAL_PORT)
   #define NUM_SERIAL 1
 #else
   #define NUM_SERIAL 0
   #undef BAUD_RATE_GCODE
 #endif
-#if SERIAL_PORT == -1 || SERIAL_PORT_2 == -1
+#if SERIAL_PORT == -1 || SERIAL_PORT_2 == -1 || SERIAL_PORT_3 == -1
   #define HAS_USB_SERIAL 1
 #endif
 #if SERIAL_PORT_2 == -2
diff --git a/Marlin/src/inc/Conditionals_post.h b/Marlin/src/inc/Conditionals_post.h
index d5bb3e68db..c237e6edd9 100644
--- a/Marlin/src/inc/Conditionals_post.h
+++ b/Marlin/src/inc/Conditionals_post.h
@@ -1859,6 +1859,7 @@
 // Flag the indexed hardware serial ports in use
 #define CONF_SERIAL_IS(N) (  (defined(SERIAL_PORT)      && SERIAL_PORT == N) \
                           || (defined(SERIAL_PORT_2)    && SERIAL_PORT_2 == N) \
+                          || (defined(SERIAL_PORT_3)    && SERIAL_PORT_3 == N) \
                           || (defined(MMU2_SERIAL_PORT) && MMU2_SERIAL_PORT == N) \
                           || (defined(LCD_SERIAL_PORT)  && LCD_SERIAL_PORT == N) )
 
diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h
index 6959e07f12..2cc90e0e8b 100644
--- a/Marlin/src/inc/SanityCheck.h
+++ b/Marlin/src/inc/SanityCheck.h
@@ -607,6 +607,14 @@
   #error "SERIAL_PORT must be defined."
 #elif defined(SERIAL_PORT_2) && SERIAL_PORT_2 == SERIAL_PORT
   #error "SERIAL_PORT_2 cannot be the same as SERIAL_PORT."
+#elif defined(SERIAL_PORT_3)
+  #ifndef SERIAL_PORT_2
+    #error "Use SERIAL_PORT_2 before using SERIAL_PORT_3"
+  #elif SERIAL_PORT_3 == SERIAL_PORT
+    #error "SERIAL_PORT_3 cannot be the same as SERIAL_PORT."
+  #elif SERIAL_PORT_3 == SERIAL_PORT_2
+    #error "SERIAL_PORT_3 cannot be the same as SERIAL_PORT_2."
+  #endif
 #endif
 #if !(defined(__AVR__) && defined(USBCON))
   #if ENABLED(SERIAL_XON_XOFF) && RX_BUFFER_SIZE < 1024
diff --git a/buildroot/tests/LPC1768 b/buildroot/tests/LPC1768
index 6b9f6aaac3..92fda54483 100755
--- a/buildroot/tests/LPC1768
+++ b/buildroot/tests/LPC1768
@@ -14,7 +14,7 @@ set -e
 #exec_test $1 $2 "Default Configuration" "$3"
 
 restore_configs
-opt_set MOTHERBOARD BOARD_RAMPS_14_RE_ARM_EFB NEOPIXEL_PIN P1_16
+opt_set MOTHERBOARD BOARD_RAMPS_14_RE_ARM_EFB NEOPIXEL_PIN P1_16 SERIAL_PORT_3 3
 opt_enable VIKI2 SDSUPPORT SDCARD_READONLY SERIAL_PORT_2 NEOPIXEL_LED
 exec_test $1 $2 "ReARM EFB VIKI2, SDSUPPORT, 2 Serial ports (USB CDC + UART0), NeoPixel" "$3"