In practice, it's difficult (and sometimes impossible) to grow the stack. To understand why requires some understanding of virtual memory.
In Ye Olde Days of single-threaded applications and contiguous memory, three were three components of a process address space: the code, the heap, and the stack. How those three were laid out depended on the OS, but generally the code came first, starting at the bottom of memory, the heap came next and grew upwards, and the stack started at the top of memory and grew downwards. There was also some memory reserved for the operating system, but we can ignore that. Programs in those days had somewhat more dramatic stack overflows: the stack would crash into the heap, and depending on which got updated first you'd either work with bad data or return from a subroutine into some arbitrary part of memory.
Memory management changed this model somewhat: from the program's perspective you still had the three components of a process memory map, and they were generally organized the same way, but now each of the components was managed as an independent segment and the MMU would signal the OS if the program tried to access memory outside a segment. Once you had virtual memory, there was no need or desire to give a program access to its entire address space. So the segments were assigned fixed boundaries.
So why isn't it desirable to give a program access to its full address space? Because that memory constitutes a "commit charge" against the swap; at any time any or all of the memory for one program might have to be written to swap to make room for another program's memory. If every program could potentially consume 2GB of swap, then either you'd have to provide enough swap for all of your programs or take the chance that two programs would need more than they could get.
At this point, assuming sufficient virtual address space, you could extend these segments if needed, and the data segment (heap) does in fact grow over time: you start out with a small data segment, and when the memory allocator requests more space when it's needed. At this point, with a single stack, it would have been physically possible to extend the stack segment: the OS could trap the attempt to push something outside of the segment and add more memory. But this isn't particularly desirable either.
Enter multi-threading. In this case, each thread has an independent stack segment, again fixed size. But now the segments are laid out one after another in the virtual address space, so there's no way to expand one segment without moving another -- which you can't do because the program will potentially have pointers to memory living in the stack. You could alternatively leave some space between segments, but that space would be wasted in almost all cases. A better approach was to put the burden on the application developer: if you really needed deep stacks, you could specify that when creating the thread.
Today, with a 64-bit virtual address space, we could create effectively infinite stacks for effectively infinite numbers of threads. But again, that isn't particularly desirable: in almost all cases, a stack overlow indicates a bug with your code. Providing you a 1 GB stack simply defers discovery of that bug.