user66119: 4th Update:
I bought an ESP32 (the successor to ESP8266) and am getting the same problem. I used the same code except used the Wifi.h library instead, also removed the ESP8266WiFiGratuitous.h code. I added an LED flash at the start of the loop and can confirm the LED stops flashing, so I can confirm the loop() stops running.
This makes me think it must be something to do with my WIFI network.
3rd update: 
I've removed all the code and instead uploaded a simple onboard LED blink sketch. The LED has been blinking for over 24 hrs which leads me to think there is something in the wifi code that is making the software crash.
2nd update:
I've even tried resetting the board at the start of the loop() body, every 10 mins, using the millisDelay.h library, calling this method to reset:
void(* resetFunc) (void) = 0;

but that doesn't help either. The loop() method seems to completey stop running.
Update:
I've added experimental::ESP8266WiFiGratuitous::stationKeepAliveSetIntervalMs(); but am still failing to get a response after 1hr, with about 15 requests made in that hour.
I have this code which works on my ESP8266:
#include <ESP8266WiFi.h>
#include <ESP8266WiFiGratuitous.h>

const size_t input_buffer_length = 256;

const char* ssid = "iiNetC2DAD7";
const char* wifiPassword = "aM9PrxcbtkS";
const char* passwordToOpenDoor = "/87"; //password should begin with a slash

const int doorPin = 5;
const int buzzerPin = 4;
const int toneDuration = 700;

int numOfRequests = 0;

WiFiServer server(301); //Pick any port number you like
WiFiClient client;

void setup() {
  Serial.begin(115200);
  delay(10);
  Serial.println(WiFi.localIP());

  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, 1);

  pinMode(doorPin, OUTPUT);
  digitalWrite(doorPin, 0);

  pinMode(buzzerPin, OUTPUT);
  digitalWrite(buzzerPin, 0);

  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, wifiPassword); 

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");

  experimental::ESP8266WiFiGratuitous::stationKeepAliveSetIntervalMs();

  server.begin();
  Serial.println("Server started.");
  Serial.println(WiFi.localIP());
}

void loop() {
  client = server.available();
  if (!client) {    
    return;
  }

  while(!client.available()){
    delay(1);  
  }

  char request[input_buffer_length];
  client.readBytesUntil('\r', request, input_buffer_length);  
  Serial.println(request);

  if(strstr(request, passwordToOpenDoor)) {//Is password correct?
    GenerateResponse("Password is correct");
    OpenDoor();
    CorrectPasswordSound();
  }
  //Got a GET request and it wasn't the favicon.ico request, must have been a bad password:
  else if (!strstr(request, "favicon.ico")) {
    GenerateResponse("Password is incorrect.");
  }  
}

void OpenDoor() {
  digitalWrite(LED_BUILTIN, 0); //flash the onboard LED to help during testing.
  digitalWrite(doorPin, 1);
  delay(500);
  digitalWrite(LED_BUILTIN, 1);
  digitalWrite(doorPin, 0);
}

void GenerateResponse(const char *text) {
  Serial.println(text);
  client.print(
      "HTTP/1.1 200 OK\r\n"
      "Content-Type: text/html\r\n"
      "\r\n"
      "<!DOCTYPE HTML>\r\n"
      "<html>\r\n"
      "<br><h1><b>"
  );
  client.print(text);
  client.print("</b></h1><br><h1><b>Num of requests: ");
  client.print(++numOfRequests);  
  client.print("</b></h1></html>\r\n");
  client.flush();  
}

void CorrectPasswordSound() {
  //Play 1700, 1800, 1900Hz
  for(int i = 17; i < 20; i++){
    int frequency = i * 100;
    tone(buzzerPin, frequency);
    delay(toneDuration);
    noTone(buzzerPin);
  }
}

However, after a random amount of time, the software crashes and the device won't respond to HTTP requests anymore.
I've tested this by printing to the serial monitor every loop. It seems as though it crashes and the loop() function stops running since it stops printing to the monitor.
I noticed that it'll still reply to pings.
It won't reconnect after restarting the router, I need to press the reset button on the ESP8266 to get it to start working again.
I've tested it by sending an HTTP Get request every 5 mins for many hours (using a C# console app). Sometimes it failed after 45 mins, sometimes 7 hours.
I initially thought it was because I was using String objects, but after getting an answer to this question and testing it out with only C-strings, I still have the same problem.
I've been sending HTTP Get requests using Chrome on my phone and laptop, as well as the .NET Framework class System.Net.Http.HttpClient.

Board: AI-Thinker ESP8266MOD
IDE: Arduino 1.8.12
ESP8266 Boards version = 2.6.3
IDE Settings: 


I've tried resetting the connection using:
server.stop();
delay(1000);
server.begin();

Then this:
ESP.reset();

Then after those 2 didn't work, this:
ESP.restart();

Resetting it frequently did nothing.
Is there anything in my code that stands out?
If there's nothing wrong withe code, then maybe my only option is to hard reset the board every 30 mins some how.

user33871: I don't know if this is your problem, but I'll leave it here because it is certainly a potential bug.  
This code in loop: 
  while(!client.available()){
    delay(1);  
  }

can cause a deadlock if you have a client that drops or doesn't talk.  Turn that around and let the loop keep running.  
boolean haveClient = false;

void loop() {
  client = server.available();
  if (client) {    
    haveClient = true;
  } else {
    haveClient = false;
  }

  if(haveClient && client.available()){

     // The rest of your loop where you read
     //  from the client goes inside here. 

That way if you get a client and then available keeps returning false you can at least keep checking for a client.  
This may not be and is not intended to be a code solution. This is probably not the best way to code what I'm trying to say.  I don't know if server.available will keep giving you back the same client or if it is going to cut you off to call that again before the client disconnects.  There may be some method in there to check the connection.  Either way, that's not the point.  The point is to never capture your code into a locked up while loop.  That is always asking for a deadlock.  It's bad practice except for cases like fatal errors where you truly want to lock the thing up.  Let the loop keep looping.  Use if statements to decide which parts get run when.  
If you want to check that this is where you are locking up, put some sort of output before and after that while loop.  Print something or turn a light on before and off after.  That way you can tell if this is the spot you're locking up.  

user66119: Thanks to Delta_G for pointing me in the right direction.
The trick was to not get stuck in the loop() function, ie. no while loops.
The problem was that after you run client = server.available();, the client needs some time before client.available() returns true, which is what the while loop is for, however, for some reason, the client would lose its connection every now and then and client.available() would never return true, in which case you get stuck in the while loop. You need to give it a second chance and let the process start again.
By delaying about 50ms, it seems to be enough time for the client to establish a connection, and if it doesn't, it loops and tries again so quickly that it appears as though the client always gets the connection and the code works fine.
Here's the working code:
void loop() {      
  WiFiClient client = server.available();

  if(client){
    delay(50);
    if(client.available()) {
        //rest of code here
    }
}


66119: <p><strong>4th Update:</strong>
I bought an ESP32 (the successor to ESP8266) and am getting the same problem. I used the same code except used the <code>Wifi.h</code> library instead, also removed the <code>ESP8266WiFiGratuitous.h</code> code. I added an LED flash at the start of the loop and can confirm the LED stops flashing, so I can confirm the <code>loop()</code> stops running.
This makes me think it must be something to do with my WIFI network.</p>

<p><strong>3rd update:</strong> 
I've removed all the code and instead uploaded a simple onboard LED blink sketch. The LED has been blinking for over 24 hrs which leads me to think there is something in the wifi code that is making the software crash.</p>

<p><strong>2nd update:</strong>
I've even tried resetting the board at the start of the <code>loop()</code> body, every 10 mins, using the millisDelay.h library, calling this method to reset:</p>

<pre><code>void(* resetFunc) (void) = 0;
</code></pre>

<p>but that doesn't help either. The <code>loop()</code> method seems to completey stop running.</p>

<p><strong>Update:</strong>
I've added <code>experimental::ESP8266WiFiGratuitous::stationKeepAliveSetIntervalMs();</code> but am still failing to get a response after 1hr, with about 15 requests made in that hour.</p>

<p>I have this code which works on my ESP8266:</p>

<pre><code>#include &lt;ESP8266WiFi.h&gt;
#include &lt;ESP8266WiFiGratuitous.h&gt;

const size_t input_buffer_length = 256;

const char* ssid = "iiNetC2DAD7";
const char* wifiPassword = "aM9PrxcbtkS";
const char* passwordToOpenDoor = "/87"; //password should begin with a slash

const int doorPin = 5;
const int buzzerPin = 4;
const int toneDuration = 700;

int numOfRequests = 0;

WiFiServer server(301); //Pick any port number you like
WiFiClient client;

void setup() {
  Serial.begin(115200);
  delay(10);
  Serial.println(WiFi.localIP());

  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, 1);

  pinMode(doorPin, OUTPUT);
  digitalWrite(doorPin, 0);

  pinMode(buzzerPin, OUTPUT);
  digitalWrite(buzzerPin, 0);

  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, wifiPassword); 

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");

  experimental::ESP8266WiFiGratuitous::stationKeepAliveSetIntervalMs();

  server.begin();
  Serial.println("Server started.");
  Serial.println(WiFi.localIP());
}

void loop() {
  client = server.available();
  if (!client) {    
    return;
  }

  while(!client.available()){
    delay(1);  
  }

  char request[input_buffer_length];
  client.readBytesUntil('\r', request, input_buffer_length);  
  Serial.println(request);

  if(strstr(request, passwordToOpenDoor)) {//Is password correct?
    GenerateResponse("Password is correct");
    OpenDoor();
    CorrectPasswordSound();
  }
  //Got a GET request and it wasn't the favicon.ico request, must have been a bad password:
  else if (!strstr(request, "favicon.ico")) {
    GenerateResponse("Password is incorrect.");
  }  
}

void OpenDoor() {
  digitalWrite(LED_BUILTIN, 0); //flash the onboard LED to help during testing.
  digitalWrite(doorPin, 1);
  delay(500);
  digitalWrite(LED_BUILTIN, 1);
  digitalWrite(doorPin, 0);
}

void GenerateResponse(const char *text) {
  Serial.println(text);
  client.print(
      "HTTP/1.1 200 OK\r\n"
      "Content-Type: text/html\r\n"
      "\r\n"
      "&lt;!DOCTYPE HTML&gt;\r\n"
      "&lt;html&gt;\r\n"
      "&lt;br&gt;&lt;h1&gt;&lt;b&gt;"
  );
  client.print(text);
  client.print("&lt;/b&gt;&lt;/h1&gt;&lt;br&gt;&lt;h1&gt;&lt;b&gt;Num of requests: ");
  client.print(++numOfRequests);  
  client.print("&lt;/b&gt;&lt;/h1&gt;&lt;/html&gt;\r\n");
  client.flush();  
}

void CorrectPasswordSound() {
  //Play 1700, 1800, 1900Hz
  for(int i = 17; i &lt; 20; i++){
    int frequency = i * 100;
    tone(buzzerPin, frequency);
    delay(toneDuration);
    noTone(buzzerPin);
  }
}
</code></pre>

<p>However, after a random amount of time, the software crashes and the device won't respond to HTTP requests anymore.</p>

<p>I've tested this by printing to the serial monitor every loop. It seems as though it crashes and the <code>loop()</code> function stops running since it stops printing to the monitor.</p>

<p>I noticed that it'll still reply to pings.</p>

<p>It won't reconnect after restarting the router, I need to press the reset button on the ESP8266 to get it to start working again.</p>

<p>I've tested it by sending an HTTP Get request every 5 mins for many hours (using a C# console app). Sometimes it failed after 45 mins, sometimes 7 hours.</p>

<p>I initially thought it was because I was using String objects, but after getting an answer to this <a href="https://arduino.stackexchange.com/questions/75450/how-to-replace-string-objects-with-char-arrays-while-still-using-string-methods">question</a> and testing it out with only C-strings, I still have the same problem.</p>

<p>I've been sending HTTP Get requests using Chrome on my phone and laptop, as well as the .NET Framework class <code>System.Net.Http.HttpClient</code>.</p>

<ul>
<li>Board: AI-Thinker ESP8266MOD</li>
<li>IDE: Arduino 1.8.12</li>
<li>ESP8266 Boards version = 2.6.3</li>
<li>IDE Settings: </li>
</ul>

<p><a href="https://i.stack.imgur.com/6oHsU.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/6oHsU.png" alt="enter image description here"></a></p>

<p>I've tried resetting the connection using:</p>

<pre><code>server.stop();
delay(1000);
server.begin();
</code></pre>

<p>Then this:</p>

<pre><code>ESP.reset();
</code></pre>

<p>Then after those 2 didn't work, this:</p>

<pre><code>ESP.restart();
</code></pre>

<p>Resetting it frequently did nothing.</p>

<p>Is there anything in my code that stands out?
If there's nothing wrong withe code, then maybe my only option is to hard reset the board every 30 mins some how.</p>
 33871: <p>I don't know if this is your problem, but I'll leave it here because it is certainly a potential bug.  </p>

<p>This code in loop: </p>

<pre><code>  while(!client.available()){
    delay(1);  
  }
</code></pre>

<p>can cause a deadlock if you have a client that drops or doesn't talk.  Turn that around and let the loop keep running.  </p>

<pre><code>boolean haveClient = false;

void loop() {
  client = server.available();
  if (client) {    
    haveClient = true;
  } else {
    haveClient = false;
  }

  if(haveClient &amp;&amp; client.available()){

     // The rest of your loop where you read
     //  from the client goes inside here. 
</code></pre>

<p>That way if you get a client and then available keeps returning false you can at least keep checking for a client.  </p>

<p>This may not be and is not intended to be a code solution. This is probably not the best way to code what I'm trying to say.  I don't know if server.available will keep giving you back the same client or if it is going to cut you off to call that again before the client disconnects.  There may be some method in there to check the connection.  Either way, that's not the point.  The point is to never capture your code into a locked up while loop.  That is always asking for a deadlock.  It's bad practice except for cases like fatal errors where you truly want to lock the thing up.  Let the loop keep looping.  Use if statements to decide which parts get run when.  </p>

<p>If you want to check that this is where you are locking up, put some sort of output before and after that while loop.  Print something or turn a light on before and off after.  That way you can tell if this is the spot you're locking up.  </p>
 66119: <p>Thanks to Delta_G for pointing me in the right direction.
The trick was to not get stuck in the <code>loop()</code> function, ie. no while loops.</p>

<p>The problem was that after you run <code>client = server.available();</code>, the client needs some time before <code>client.available()</code> returns true, which is what the while loop is for, <strong>however</strong>, for some reason, the client would lose its connection every now and then and <code>client.available()</code> would never return true, in which case you get stuck in the while loop. You need to give it a second chance and let the process start again.</p>

<p>By delaying about 50ms, it seems to be enough time for the client to establish a connection, and if it doesn't, it loops and tries again so quickly that it appears as though the client always gets the connection and the code works fine.</p>

<p>Here's the working code:</p>

<pre><code>void loop() {      
  WiFiClient client = server.available();

  if(client){
    delay(50);
    if(client.available()) {
        //rest of code here
    }
}
</code></pre>
