InfraRunBook
    Back to articles

    What Is Nginx and Why It Is So Popular

    Nginx
    Published: Mar 31, 2026
    Updated: Mar 31, 2026

    Nginx is a high-performance, event-driven web server, reverse proxy, and load balancer that has become a cornerstone of modern infrastructure. Learn how it works, why it outperforms traditional servers, and how to configure it for real-world deployments.

    What Is Nginx and Why It Is So Popular

    Nginx (pronounced "engine-x") is an open-source, high-performance web server, reverse proxy, load balancer, and HTTP cache. Originally written by Igor Sysoev and publicly released in 2004, Nginx was designed from the ground up to solve one of the most notorious scalability problems in early web infrastructure: the C10K problem — the challenge of handling ten thousand concurrent connections on a single server without degrading performance.

    Today, Nginx powers a substantial portion of the internet, serving traffic for some of the world's largest websites and acting as the backbone of countless infrastructure stacks. According to web server surveys, Nginx consistently ranks at the top of the market alongside Apache, often surpassing it in high-traffic and cloud-native deployments. Understanding what Nginx is and why it dominates is essential knowledge for any infrastructure engineer.

    The Architecture That Changed Everything

    The secret behind Nginx's performance is its event-driven, asynchronous, non-blocking architecture. Traditional web servers like Apache use a process-per-connection or thread-per-connection model. Each incoming request spawns a new process or thread, which consumes memory and CPU time even when the connection is idle — waiting for data from the client or a backend service.

    Nginx takes an entirely different approach. A single master process manages a pool of worker processes, and each worker handles thousands of connections simultaneously using an event loop. When a connection is idle, no CPU cycles are wasted. The worker moves on to handle other connections and returns only when an event fires — data arriving, a socket becoming writable, a timer expiring. This model is similar in concept to how Node.js handles asynchronous I/O.

    On a host like sw-infrarunbook-01, a single Nginx worker process can manage tens of thousands of simultaneous connections while keeping memory usage in the range of just a few megabytes. Contrast this with Apache's prefork MPM, where each connection holds a dedicated process in memory regardless of whether that connection is actively transferring data.

    Core Roles of Nginx in Production

    Nginx is not a single-purpose tool. In modern infrastructure it regularly fulfills several distinct roles simultaneously:

    • Web Server: Nginx serves static content — HTML, CSS, JavaScript, images, fonts — with extreme efficiency. Its use of the
      sendfile()
      system call allows the kernel to transfer file data directly to the network socket, bypassing user-space memory entirely.
    • Reverse Proxy: Nginx sits in front of one or more application servers, forwarding client requests and relaying responses. This decouples the public-facing network layer from backend application logic.
    • Load Balancer: Nginx distributes incoming traffic across multiple upstream servers using configurable algorithms including round-robin, least connections, IP hash, and weighted distribution.
    • SSL/TLS Terminator: Nginx handles the cryptographic overhead of HTTPS, decrypting traffic before forwarding plain HTTP to backend services over a private network segment.
    • HTTP Cache: Nginx can cache backend responses and serve them directly for subsequent identical requests, dramatically reducing load on origin application servers.
    • Rate Limiter and Access Controller: Nginx enforces per-client request rate limits, IP-based access restrictions, and connection limits to protect infrastructure from abuse.

    Installing Nginx on Linux

    On most modern Linux distributions, installing Nginx is straightforward. On a Debian or Ubuntu-based system logged in as infrarunbook-admin:

    sudo apt update
    sudo apt install nginx -y
    sudo systemctl enable nginx
    sudo systemctl start nginx
    sudo systemctl status nginx

    On Red Hat, CentOS, AlmaLinux, or Rocky Linux systems:

    sudo dnf install nginx -y
    sudo systemctl enable --now nginx
    sudo firewall-cmd --permanent --add-service=http
    sudo firewall-cmd --permanent --add-service=https
    sudo firewall-cmd --reload

    After installation, always validate that the default configuration is syntactically correct before making changes:

    sudo nginx -t
    nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
    nginx: configuration file /etc/nginx/nginx.conf test is successful

    Nginx Configuration Fundamentals

    Nginx configuration lives primarily in

    /etc/nginx/nginx.conf
    , with site-specific configurations placed under
    /etc/nginx/conf.d/
    or
    /etc/nginx/sites-available/
    . The configuration language uses a block-and-directive syntax that is both expressive and readable.

    A minimal virtual host configuration for serving the domain solvethenetwork.com on host sw-infrarunbook-01:

    server {
        listen 80;
        listen [::]:80;
        server_name solvethenetwork.com www.solvethenetwork.com;
    
        root /var/www/solvethenetwork.com/html;
        index index.html index.htm;
    
        location / {
            try_files $uri $uri/ =404;
        }
    
        access_log /var/log/nginx/solvethenetwork_access.log;
        error_log  /var/log/nginx/solvethenetwork_error.log warn;
    }

    The

    try_files
    directive is one of Nginx's most commonly used features. It checks for the requested URI as a file, then as a directory, and if neither exists, returns a 404 response. This forms the foundation of static file serving and is also used to route all unmatched paths to single-page application entry points.

    Nginx as a Reverse Proxy with SSL Termination

    One of the most common production deployments is using Nginx as a reverse proxy in front of an application server such as Gunicorn, uWSGI, Node.js, or a Java application running on an internal address. The following configuration proxies HTTPS traffic to a backend at 192.168.10.20:

    server {
        listen 443 ssl;
        server_name solvethenetwork.com;
    
        ssl_certificate     /etc/ssl/certs/solvethenetwork.com.crt;
        ssl_certificate_key /etc/ssl/private/solvethenetwork.com.key;
        ssl_protocols       TLSv1.2 TLSv1.3;
        ssl_ciphers         HIGH:!aNULL:!MD5;
        ssl_session_cache   shared:SSL:10m;
        ssl_session_timeout 10m;
    
        location / {
            proxy_pass         http://192.168.10.20:8080;
            proxy_set_header   Host              $host;
            proxy_set_header   X-Real-IP         $remote_addr;
            proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
            proxy_set_header   X-Forwarded-Proto $scheme;
            proxy_read_timeout 90s;
            proxy_connect_timeout 30s;
        }
    }

    The

    proxy_set_header
    directives are critical. Without them, the backend application sees Nginx's internal IP as the client address, losing original client IP information. The
    X-Forwarded-For
    header preserves the chain of proxy addresses, while
    X-Real-IP
    captures the direct connecting client address. The
    ssl_session_cache
    directive enables TLS session resumption, significantly reducing handshake overhead for returning clients.

    Upstream Load Balancing

    When an application outgrows a single backend server, Nginx's upstream module provides built-in load balancing. The following configuration distributes traffic across three backend application servers on RFC 1918 addresses:

    upstream app_backend {
        least_conn;
        server 192.168.10.21:8080 weight=3;
        server 192.168.10.22:8080 weight=3;
        server 192.168.10.23:8080 weight=1 backup;
    
        keepalive 32;
    }
    
    server {
        listen 443 ssl;
        server_name solvethenetwork.com;
    
        ssl_certificate     /etc/ssl/certs/solvethenetwork.com.crt;
        ssl_certificate_key /etc/ssl/private/solvethenetwork.com.key;
    
        location /api/ {
            proxy_pass http://app_backend;
            proxy_http_version 1.1;
            proxy_set_header Connection "";
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
        }
    }

    The

    least_conn
    directive selects the backend with the fewest active connections — ideal for workloads with variable request duration. The server at 192.168.10.23 is marked as a backup, only receiving traffic when the primary servers are unhealthy or unavailable. The
    keepalive 32
    directive maintains a pool of 32 persistent connections to upstream servers, eliminating per-request TCP handshake overhead at high throughput.

    Rate Limiting to Protect Your Infrastructure

    Nginx's

    limit_req
    module provides powerful request rate limiting. This protects APIs from abuse, mitigates brute-force login attempts, and ensures fair resource allocation across clients. The following example limits the authentication endpoint to 5 requests per minute per client IP:

    http {
        limit_req_zone $binary_remote_addr zone=login_limit:10m rate=5r/m;
    
        server {
            listen 443 ssl;
            server_name solvethenetwork.com;
    
            location /auth/login {
                limit_req zone=login_limit burst=3 nodelay;
                limit_req_status 429;
                proxy_pass http://192.168.10.20:8080;
            }
        }
    }

    The

    limit_req_zone
    directive creates a 10-megabyte shared memory zone named
    login_limit
    , keyed on the client's binary IP address. The
    burst=3
    parameter allows short bursts of up to 3 additional requests before enforcing the limit strictly. The
    nodelay
    flag ensures burst requests are processed immediately rather than queued, and
    limit_req_status 429
    returns the semantically correct HTTP 429 Too Many Requests response code.

    Gzip Compression

    Enabling gzip compression reduces the size of HTTP responses significantly — typically 60–80% for text-based content. This reduces bandwidth consumption and improves perceived page load times. A production-ready gzip configuration block:

    http {
        gzip on;
        gzip_vary on;
        gzip_proxied any;
        gzip_comp_level 6;
        gzip_buffers 16 8k;
        gzip_http_version 1.1;
        gzip_min_length 256;
        gzip_types
            text/plain
            text/css
            text/xml
            text/javascript
            application/json
            application/javascript
            application/xml
            image/svg+xml;
    }

    A

    gzip_comp_level
    of 6 is the well-tested sweet spot between compression ratio and CPU cost. Level 9 yields marginally better compression at significantly higher CPU expense. The
    gzip_min_length 256
    setting prevents compressing very small responses where the compression header overhead would exceed any savings in body size.

    Security Headers

    Nginx makes it straightforward to enforce modern HTTP security headers across all responses, instructing browsers to apply additional protections against common attack vectors:

    server {
        listen 443 ssl;
        server_name solvethenetwork.com;
    
        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
        add_header X-Frame-Options "SAMEORIGIN" always;
        add_header X-Content-Type-Options "nosniff" always;
        add_header X-XSS-Protection "1; mode=block" always;
        add_header Referrer-Policy "strict-origin-when-cross-origin" always;
        add_header Content-Security-Policy "default-src 'self'; script-src 'self'" always;
        add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
    
        server_tokens off;
    }

    The

    always
    parameter on each
    add_header
    directive ensures headers are included in all responses — including error responses — not only 2xx and 3xx status codes. The
    server_tokens off
    directive prevents Nginx from exposing its version number in the
    Server
    response header and error pages, reducing the information surface available to attackers conducting reconnaissance.

    Custom Log Formats for Observability

    Nginx's default log format is functional but limited for production observability. A custom format can include upstream timing, cache status, SSL protocol, and request timing data useful for SLO monitoring:

    http {
        log_format detailed '$remote_addr - $remote_user [$time_local] '
                            '"$request" $status $body_bytes_sent '
                            '"$http_referer" "$http_user_agent" '
                            'rt=$request_time uct=$upstream_connect_time '
                            'uht=$upstream_header_time urt=$upstream_response_time '
                            'cs=$upstream_cache_status ssl=$ssl_protocol';
    
        server {
            access_log /var/log/nginx/solvethenetwork_detailed.log detailed;
        }
    }

    The

    $request_time
    variable captures total elapsed time from receiving the first byte of the request to sending the last byte of the response — the true end-to-end latency from Nginx's perspective. The
    $upstream_response_time
    measures only the time waiting for the backend, allowing engineers to isolate whether latency originates in the proxy layer, the network, or the application itself.

    The Nginx Worker Process Model

    A typical running Nginx instance on sw-infrarunbook-01 shows the master-worker model clearly:

    ps aux | grep nginx
    root       1001  0.0  0.1  55680  2048 ?  Ss   09:00   0:00 nginx: master process /usr/sbin/nginx
    www-data   1002  0.0  0.3 111360  6144 ?  S    09:00   0:02 nginx: worker process
    www-data   1003  0.0  0.3 111360  6144 ?  S    09:00   0:01 nginx: worker process
    www-data   1004  0.0  0.3 111360  6144 ?  S    09:00   0:02 nginx: worker process
    www-data   1005  0.0  0.3 111360  6144 ?  S    09:00   0:01 nginx: worker process

    The master process runs as root, binds to privileged ports, reads configuration, and manages worker lifecycle. Workers run as an unprivileged user (

    www-data
    ), handling all actual client connections. If a worker crashes — rare, but possible with buggy third-party modules — the master immediately spawns a replacement. This design provides both security isolation and operational resilience.

    Recommended worker configuration for a production server:

    worker_processes auto;
    worker_rlimit_nofile 65535;
    
    events {
        worker_connections 4096;
        use epoll;
        multi_accept on;
    }

    Setting

    worker_processes auto
    causes Nginx to spawn one worker per logical CPU core, the optimal ratio for CPU-bound workloads. With 4 workers at 4096 connections each, the server supports up to 16,384 simultaneous connections. The
    use epoll
    directive selects Linux's most efficient I/O event notification mechanism, and
    multi_accept on
    allows each worker to accept all pending connections in a single call rather than one at a time.

    Nginx in the Modern Microservice Stack

    In microservice environments, Nginx on sw-infrarunbook-01 at 192.168.10.10 commonly acts as an API gateway routing requests to multiple services based on URI prefix:

    upstream user_service {
        server 192.168.10.31:3001;
        server 192.168.10.32:3001;
    }
    
    upstream product_service {
        server 192.168.10.33:3002;
        server 192.168.10.34:3002;
    }
    
    upstream order_service {
        server 192.168.10.35:3003;
        keepalive 16;
    }
    
    server {
        listen 443 ssl;
        server_name api.solvethenetwork.com;
    
        ssl_certificate     /etc/ssl/certs/solvethenetwork.com.crt;
        ssl_certificate_key /etc/ssl/private/solvethenetwork.com.key;
    
        location /v1/users/ {
            proxy_pass http://user_service;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
        }
    
        location /v1/products/ {
            proxy_pass http://product_service;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
        }
    
        location /v1/orders/ {
            proxy_pass http://order_service;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
        }
    }

    This pattern consolidates TLS termination, routing logic, and observability into a single layer, allowing backend services to communicate over plain HTTP on the internal

    192.168.10.0/24
    network segment without each service needing its own certificate management.

    Why Nginx Has Endured and Thrived

    Nginx's sustained dominance comes down to qualities that infrastructure engineers care about deeply:

    1. Predictable Performance Under Load: Nginx's memory footprint grows slowly and linearly with connection count. At 1,000 concurrent connections or 100,000, memory usage remains predictable — a property that makes capacity planning straightforward and prevents unexpected OOM conditions during traffic spikes.
    2. Configuration Expressiveness: The configuration language is powerful and composable. Complex routing logic, header manipulation, caching policies, and access control can be expressed in relatively few lines with no scripting required.
    3. Zero-Downtime Reloads: Configuration changes can be applied with a graceful reload — the master process spawns new workers with updated config while existing workers finish in-flight requests. Deployments never require dropping active connections.
    4. Community and Ecosystem: A vast body of documentation, community-contributed configurations, and integration guides exist for every application stack. Tooling like Certbot has first-class Nginx support, making automated SSL certificate provisioning and renewal trivial.
    5. Proven at Scale: Nginx has been battle-tested at organizations serving billions of requests per day. Its reliability at internet scale is not theoretical — it is continuously demonstrated in production environments worldwide.

    Whether you are serving a personal project, scaling a growing SaaS platform, or architecting systems for enterprise-level traffic on solvethenetwork.com, Nginx offers a proven, efficient, and flexible foundation. Its event-driven core, rich feature set, and operational simplicity make it one of the most enduring and valuable tools in the modern infrastructure engineer's arsenal.


    Frequently Asked Questions

    Q: What does Nginx stand for?

    A: Nginx stands for "engine-x." The name combines "engine" with the letter "X," which commonly denotes cross-platform or open-ended capability in software naming. It is pronounced "engine-ex" and was chosen by its creator Igor Sysoev to reflect the high-performance engine concept at its core.

    Q: Is Nginx free to use in production?

    A: Yes. Nginx is open-source software distributed under the 2-clause BSD license, which permits free use in personal and commercial production environments without royalty or restriction. A commercial variant called Nginx Plus exists with additional enterprise features and official vendor support, but the open-source version is fully functional for the vast majority of production deployments.

    Q: How is Nginx different from Apache?

    A: The primary architectural difference is concurrency handling. Apache's prefork MPM spawns a dedicated process per connection; Nginx uses an asynchronous event loop where a single worker handles thousands of connections simultaneously. This makes Nginx far more memory-efficient at high concurrency. Apache supports per-directory configuration via .htaccess files — useful in shared hosting — while Nginx requires all configuration changes to go through a server-level reload. For high-traffic, API-driven, or container-based workloads, Nginx is typically the better choice.

    Q: Can Nginx serve PHP applications directly?

    A: Nginx does not execute PHP internally. Instead, it delegates PHP processing to an external FastCGI process manager — most commonly PHP-FPM. Nginx passes requests matching

    .php
    files to PHP-FPM via the FastCGI protocol and returns the processed output to the client. This architectural separation allows PHP workers to be tuned, restarted, and scaled independently from the web server, which is generally considered a cleaner and more scalable design than Apache's embedded mod_php approach.

    Q: How many concurrent connections can a single Nginx instance handle?

    A: On modern hardware, a single Nginx instance can handle hundreds of thousands to over a million concurrent connections depending on traffic characteristics and available system resources. The per-worker limit is set by

    worker_connections
    , and total theoretical capacity equals
    worker_processes × worker_connections
    . A server like sw-infrarunbook-01 with 4 workers at 4096 connections each supports up to 16,384 simultaneous connections, though real-world limits are constrained by available memory, file descriptor limits, and network capacity.

    Q: What is the difference between Nginx open source and Nginx Plus?

    A: Nginx Plus is the commercially supported product built on the open-source core. It adds active health checks that proactively probe upstream servers rather than relying on passive failure detection, a live REST API for dynamic upstream configuration without reloads, session persistence (sticky sessions), JWT authentication, enhanced real-time monitoring dashboards, and official F5/Nginx support contracts. For organizations with SLA requirements or complex upstream management needs, Nginx Plus is worth evaluating. For most infrastructure teams, the open-source version covers all common requirements.

    Q: How do I reload Nginx configuration without dropping active connections?

    A: Use

    sudo nginx -s reload
    or
    sudo systemctl reload nginx
    . The reload signal instructs the master process to re-read and validate the configuration, then spawn new worker processes using the updated configuration while existing workers continue to serve in-flight requests until they complete naturally. This is a zero-downtime operation. Always run
    sudo nginx -t
    first to validate configuration syntax before issuing a reload, as a syntax error would prevent new workers from starting and could interrupt traffic if old workers have already exited.

    Q: What is the Nginx master process responsible for?

    A: The master process performs several functions: it reads and validates configuration files, binds to privileged ports (80 and 443 require root), starts and supervises worker processes, handles operating system signals for reload and graceful shutdown, and facilitates zero-downtime binary upgrades. The master process never handles client connections directly — all connection processing is the exclusive responsibility of worker processes running as an unprivileged user.

    Q: Can Nginx proxy TCP and UDP traffic, not just HTTP?

    A: Yes. The

    stream
    module provides TCP and UDP proxying and load balancing for non-HTTP protocols. This enables Nginx to proxy MySQL connections to 192.168.10.40:3306, Redis to 192.168.10.41:6379, or any custom TCP-based protocol. The stream block uses syntax similar to the http block with upstream and server directives. This makes Nginx a versatile general-purpose Layer 4 proxy, not solely a web server.

    Q: What is the purpose of the try_files directive?

    A: The

    try_files
    directive instructs Nginx to test for the existence of files or directories in sequence, falling back to a final option if none match. In static serving,
    try_files $uri $uri/ =404
    checks for a matching file, then a matching directory, then returns 404. In single-page application deployments,
    try_files $uri $uri/ /index.html
    serves the SPA's root entry point for any unmatched path, enabling client-side routing frameworks to function correctly without server-side route handling.

    Q: How does Nginx handle large file uploads?

    A: The key directive for large uploads is

    client_max_body_size
    , which defaults to 1MB. For larger uploads, increase it to match the expected maximum — for example,
    client_max_body_size 500m
    for 500MB payloads. Also tune
    client_body_timeout
    to accommodate slow upload connections, and set
    proxy_read_timeout
    high enough for backends that take time to process large files. For very large uploads, set
    proxy_request_buffering off
    to stream the request body directly to the upstream server rather than buffering the entire payload in Nginx's memory first.

    Q: How can I test that my Nginx security headers are correctly configured?

    A: Use

    curl -I https://solvethenetwork.com
    to inspect response headers directly from the command line on sw-infrarunbook-01. Look for the presence of
    Strict-Transport-Security
    ,
    X-Frame-Options
    ,
    X-Content-Type-Options
    , and
    Content-Security-Policy
    in the output. Also check that the
    Server
    header shows only "nginx" with no version number, confirming that
    server_tokens off
    is active. Tools like Mozilla Observatory provide structured scoring of security header completeness and can flag missing or misconfigured policies.

    Frequently Asked Questions

    What does Nginx stand for?

    Nginx stands for "engine-x." The name combines "engine" with the letter "X," which commonly denotes cross-platform or open-ended capability in software naming. It is pronounced "engine-ex" and was chosen by its creator Igor Sysoev to reflect the high-performance engine concept at its core.

    Is Nginx free to use in production?

    Yes. Nginx is open-source software distributed under the 2-clause BSD license, which permits free use in personal and commercial production environments without royalty or restriction. A commercial variant called Nginx Plus exists with additional enterprise features and official vendor support, but the open-source version is fully functional for the vast majority of production deployments.

    How is Nginx different from Apache?

    The primary architectural difference is concurrency handling. Apache's prefork MPM spawns a dedicated process per connection; Nginx uses an asynchronous event loop where a single worker handles thousands of connections simultaneously. This makes Nginx far more memory-efficient at high concurrency. Apache supports per-directory configuration via .htaccess files, while Nginx requires all configuration changes to go through a server-level reload. For high-traffic, API-driven, or container-based workloads, Nginx is typically the better choice.

    Can Nginx serve PHP applications directly?

    Nginx does not execute PHP internally. Instead, it delegates PHP processing to an external FastCGI process manager — most commonly PHP-FPM. Nginx passes requests matching .php files to PHP-FPM via the FastCGI protocol and returns the processed output to the client. This architectural separation allows PHP workers to be tuned, restarted, and scaled independently from the web server.

    How many concurrent connections can a single Nginx instance handle?

    On modern hardware, a single Nginx instance can handle hundreds of thousands to over a million concurrent connections depending on traffic characteristics and available system resources. The per-worker limit is set by worker_connections, and total theoretical capacity equals worker_processes multiplied by worker_connections. Real-world limits are constrained by available memory, file descriptor limits, and network capacity.

    What is the difference between Nginx open source and Nginx Plus?

    Nginx Plus adds active health checks, a live REST API for dynamic upstream configuration without reloads, session persistence, JWT authentication, enhanced real-time monitoring dashboards, and official vendor support contracts. For most infrastructure teams, the open-source version covers all common production requirements.

    How do I reload Nginx configuration without dropping active connections?

    Use sudo nginx -s reload or sudo systemctl reload nginx. The reload signal causes the master process to spawn new workers with updated configuration while existing workers finish serving in-flight requests naturally. Always run sudo nginx -t first to validate configuration syntax before issuing a reload to avoid interrupting traffic with a broken config.

    What is the Nginx master process responsible for?

    The master process reads and validates configuration, binds to privileged ports, starts and supervises worker processes, handles signals for reload and graceful shutdown, and facilitates zero-downtime binary upgrades. It never handles client connections directly — all connection processing is handled exclusively by unprivileged worker processes.

    Can Nginx proxy TCP and UDP traffic, not just HTTP?

    Yes. The stream module provides TCP and UDP proxying and load balancing for non-HTTP protocols such as MySQL, PostgreSQL, Redis, SMTP, and custom TCP-based protocols. The stream block uses syntax similar to the http block and makes Nginx a versatile general-purpose Layer 4 proxy, not solely a web server.

    What is the purpose of the try_files directive?

    The try_files directive instructs Nginx to test for the existence of files or directories in sequence, falling back to a final option if none match. In static file serving it checks for a matching file, then a directory, then returns 404. In single-page application deployments it serves the root index.html for any unmatched path, enabling client-side routing frameworks to work correctly.

    Related Articles