如何在 Ubuntu 16.04 上为 Nginx 创建自签名 SSL 证书
简介
TLS,即传输层安全协议,及其前身SSL,即安全套接字层,是用于将普通流量包装在受保护的加密包装中的网络协议。
使用这项技术,服务器可以在服务器和客户端之间安全地发送流量,而不会被外部方拦截。证书系统还帮助用户验证他们正在连接的站点的身份。
在本指南中,我们将向您展示如何在 Ubuntu 16.04 服务器上为 Nginx web 服务器设置自签名 SSL 证书。
先决条件
在开始之前,您应该已经配置了一个具有sudo
权限的非根用户。您可以按照我们的 Ubuntu 16.04 初始服务器设置指南来了解如何设置此类用户帐户。
您还需要已安装 Nginx web 服务器。如果您想在服务器上安装完整的 LEMP(Linux、Nginx、MySQL、PHP)堆栈,可以按照我们的 Ubuntu 16.04 上设置 LEMP 的指南进行操作。
如果您只想要 Nginx web 服务器,可以按照我们的 Ubuntu 16.04 上安装 Nginx 的指南进行操作。
完成先决条件后,请继续以下操作。
步骤 1:创建 SSL 证书
TLS/SSL 通过使用公共证书和私钥的组合来工作。SSL 密钥在服务器上保密。它用于加密发送给客户端的内容。SSL 证书与请求内容的任何人公开共享。它可用于解密由相关 SSL 密钥签名的内容。
我们可以使用 OpenSSL 一次性创建自签名密钥和证书对:
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/private/nginx-selfsigned.key -out /etc/ssl/certs/nginx-selfsigned.crt
您将被要求回答一系列问题。在我们讨论这些问题之前,让我们看看我们正在发出的命令中发生了什么:
- openssl:这是用于创建和管理 OpenSSL 证书、密钥和其他文件的基本命令行工具。
- req:此子命令指定我们要使用 X.509 证书签名请求(CSR)管理。“X.509” 是 SSL 和 TLS 遵循的用于其密钥和证书管理的公钥基础设施标准。我们要创建一个新的 X.509 证书,因此我们正在使用此子命令。
- -x509:这通过告诉实用程序我们要创建自签名证书,而不是生成证书签名请求,进一步修改了前一个子命令。
- -nodes:这告诉 OpenSSL 跳过使用密码短语保护我们的证书的选项。我们需要 Nginx 能够在服务器启动时无需用户干预地读取文件。密码短语会阻止这种情况发生,因为我们每次重新启动后都需要输入密码。
- -days 365:此选项设置证书被视为有效的时间长度。我们在这里设置为一年。
- -newkey rsa:2048:这指定我们要同时生成新证书和新密钥。我们没有在之前的步骤中创建用于签署证书的密钥,因此我们需要同时创建它和证书。
rsa:2048
部分告诉它生成一个长度为 2048 位的 RSA 密钥。 - -keyout:此行告诉 OpenSSL 在哪里放置我们正在创建的生成私钥文件。
- -out:这告诉 OpenSSL 在哪里放置我们正在创建的证书。
正如我们上面所述,这些选项将创建一个密钥文件和一个证书。我们将被要求关于我们的服务器的一些问题,以便将信息正确嵌入证书中。
适当填写提示。最重要的一行是请求“通用名称(例如服务器 FQDN 或您的名称)”。您需要输入与您的服务器关联的域名或更可能是您服务器的公共 IP 地址。
提示的全部内容将类似于以下内容:
Country Name (2 letter code) [AU]:US
State or Province Name (full name) [Some-State]:New York
Locality Name (eg, city) []:New York City
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Bouncy Castles, Inc.
Organizational Unit Name (eg, section) []:Ministry of Water Slides
Common Name (e.g. server FQDN or YOUR name) []:server_IP_address
Email Address []:admin@your_domain.com
您创建的两个文件将放置在/etc/ssl
目录的适当子目录中。
当我们使用 OpenSSL 时,我们还应该创建一个强大的 Diffie-Hellman 组,用于与客户端协商完美前向保密。
我们可以通过输入以下内容来实现:
sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048
这可能需要几分钟的时间,但完成后,您将在/etc/ssl/certs/dhparam.pem
上拥有一个强大的 DH 组,我们可以在我们的配置中使用。
步骤 2:配置 Nginx 使用 SSL
我们已经在 /etc/ssl
目录下创建了我们的密钥和证书文件。现在我们只需要修改我们的 Nginx 配置以利用这些文件。
我们将对我们的配置进行一些调整。
- 我们将创建一个包含 SSL 密钥和证书文件位置的配置片段。
- 我们将创建一个包含强大 SSL 设置的配置片段,这些设置可以在将来与任何证书一起使用。
- 我们将调整我们的 Nginx 服务器块以处理 SSL 请求,并使用上述两个片段。
这种配置 Nginx 的方法将允许我们保持清晰的服务器块,并将常见的配置段放入可重用的模块中。
创建指向 SSL 密钥和证书的配置片段
首先,让我们在 /etc/nginx/snippets
目录下创建一个新的 Nginx 配置片段。
为了正确区分该文件的目的,让我们将其命名为 self-signed.conf
:
sudo nano /etc/nginx/snippets/self-signed.conf
在这个文件中,我们只需要将 ssl_certificate
指令设置为我们的证书文件,将 ssl_certificate_key
设置为相关的密钥。在我们的情况下,这将如下所示:
ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt;
ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key;
当您添加了这些行后,保存并关闭文件。
创建包含强加密设置的配置片段
接下来,我们将创建另一个片段,用于定义一些 SSL 设置。这将使用强大的 SSL 密码套件并启用一些高级功能,有助于保持我们的服务器安全。
我们将设置的参数可以在将来的 Nginx 配置中重复使用,因此我们将给文件一个通用的名称:
sudo nano /etc/nginx/snippets/ssl-params.conf
为了安全地设置 Nginx SSL,我们将使用 Remy van Elst 在 Cipherli.st 网站上的建议。该网站旨在为流行软件提供易于消化的加密设置。您可以在这里关于他在 Nginx 选择方面的决定。
对于我们的目的,我们可以完全复制所提供的设置。我们只需要做一些小的修改。
首先,我们将添加我们首选的上游请求的 DNS 解析器。在本指南中,我们将使用 Google 的解析器。我们还将设置 ssl_dhparam
设置,指向我们之前生成的 Diffie-Hellman 文件。
最后,您应该花一点时间了解 HTTP 严格传输安全性(HSTS),特别是关于“preload”功能。预加载 HSTS 提供了增强的安全性,但如果意外启用或错误启用可能会产生深远的后果。在本指南中,我们不会预加载这些设置,但如果您确信理解了其影响,可以进行修改:
# 来自 https://cipherli.st/
# 和 https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.htmlssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
ssl_ecdh_curve secp384r1;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# 暂时禁用预加载 HSTS。如果您理解其影响,可以使用包含“preload”指令的已注释的头行。
#add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;ssl_dhparam /etc/ssl/certs/dhparam.pem;
因为我们使用的是自签名证书,所以 SSL stapling 将不会被使用。Nginx 将简单地输出警告,为我们的自签名证书禁用 stapling,并继续正常运行。
完成后保存并关闭文件。
调整 Nginx 配置以使用 SSL
现在我们有了我们的片段,我们可以调整我们的 Nginx 配置以启用 SSL。
在本指南中,我们假设您正在使用 /etc/nginx/sites-available
目录中的 default
服务器块文件。如果您使用不同的服务器块文件,请在下面的命令中替换其名称。
在继续之前,让我们先备份当前的服务器块文件:
sudo cp /etc/nginx/sites-available/default /etc/nginx/sites-available/default.bak
现在,打开服务器块文件进行调整:
sudo nano /etc/nginx/sites-available/default
在文件中,您的服务器块可能开始如下所示:
server {listen 80 default_server;listen [::]:80 default_server;# SSL configuration# listen 443 ssl default_server;# listen [::]:443 ssl default_server;. . .
我们将修改此配置,以便将未加密的 HTTP 请求自动重定向到加密的 HTTPS。这为我们的站点提供了最佳安全性。如果您希望允许 HTTP 和 HTTPS 流量,请使用以下备用配置。
我们将把配置分成两个单独的块。在两个第一条 listen
指令之后,我们将添加一个 server_name
指令,设置为您的服务器域名或更可能的 IP 地址。然后,我们将设置一个重定向到我们将要创建的第二个服务器块。之后,我们将关闭这个简短的块:
server {listen 80 default_server;listen [::]:80 default_server;server_name server_domain_or_IP;return 302 https://$server_name$request_uri;
}# SSL configuration# listen 443 ssl default_server;# listen [::]:443 ssl default_server;. . .
接下来,我们需要在下面直接开始一个新的服务器块,以包含剩余的配置。我们可以取消注释使用端口 443 的两个 listen
指令。我们可以在这些行中添加 http2
以在此块中启用 HTTP/2。之后,我们只需要包含我们设置的两个片段文件:
server {listen 80 default_server;listen [::]:80 default_server;server_name server_domain_or_IP;return 302 https://$server_name$request_uri;
}server {# SSL configurationlisten 443 ssl http2 default_server;listen [::]:443 ssl http2 default_server;include snippets/self-signed.conf;include snippets/ssl-params.conf;. . .
完成后保存并关闭文件。
(备用配置)允许 HTTP 和 HTTPS 流量
如果您希望或需要允许加密和非加密内容,您将需要稍微不同地配置 Nginx。一般来说,如果可以避免,这通常是不建议的,但在某些情况下可能是必要的。基本上,我们将两个单独的服务器块压缩成一个块,并删除重定向:
server {listen 80 default_server;listen [::]:80 default_server;listen 443 ssl http2 default_server;listen [::]:443 ssl http2 default_server;server_name server_domain_or_IP;include snippets/self-signed.conf;include snippets/ssl-params.conf;. . .
完成后保存并关闭文件。
步骤 3:调整防火墙
如果您已经启用了 ufw
防火墙,如先决指南中推荐的那样,您需要调整设置以允许 SSL 流量。幸运的是,Nginx 在安装时会向 ufw
注册一些配置文件。
我们可以通过输入以下命令来查看可用的配置文件:
sudo ufw app list
您应该会看到以下类似的列表:
Available applications:Nginx FullNginx HTTPNginx HTTPSOpenSSH
您可以通过输入以下命令来查看当前设置:
sudo ufw status
它可能看起来像这样,意味着只有 HTTP 流量被允许访问 Web 服务器:
Status: activeTo Action From
-- ------ ----
OpenSSH ALLOW Anywhere
Nginx HTTP ALLOW Anywhere
OpenSSH (v6) ALLOW Anywhere (v6)
Nginx HTTP (v6) ALLOW Anywhere (v6)
为了额外允许 HTTPS 流量,我们可以允许 “Nginx Full” 配置文件,然后删除多余的 “Nginx HTTP” 配置文件允许:
sudo ufw allow 'Nginx Full'
sudo ufw delete allow 'Nginx HTTP'
现在您的状态应该是这样的:
sudo ufw status
Status: activeTo Action From
-- ------ ----
OpenSSH ALLOW Anywhere
Nginx Full ALLOW Anywhere
OpenSSH (v6) ALLOW Anywhere (v6)
Nginx Full (v6) ALLOW Anywhere (v6)
步骤 4:启用 Nginx 中的更改
现在我们已经做出了更改并调整了防火墙,我们可以重新启动 Nginx 来实施我们的新更改。
首先,我们应该检查我们的文件中是否有语法错误。我们可以通过输入以下命令来执行此操作:
sudo nginx -t
如果一切顺利,您将会得到以下类似的结果:
nginx: [warn] "ssl_stapling" ignored, issuer certificate not found
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
请注意开头的警告。如前所述,由于我们的自签名证书无法使用 SSL stapling,因此此特定设置会产生警告。这是预期的,我们的服务器仍然可以正确加密连接。
如果您的输出与上述相匹配,则您的配置文件没有语法错误。我们可以安全地重新启动 Nginx 来实施我们的更改:
sudo systemctl restart nginx
步骤 5:测试加密
现在,我们准备测试我们的 SSL 服务器。
打开您的 Web 浏览器,然后在地址栏中输入 https://
,后面跟上您服务器的域名或 IP:
https://server_domain_or_IP
由于我们创建的证书未经您浏览器信任的证书颁发机构签名,您可能会看到以下类似的警告:
!Nginx self-signed cert warning
这是预期的和正常的。我们只关心证书的加密方面,而不是主机真实性的第三方验证。点击 “高级”,然后点击提供的链接以继续访问您的主机:
!Nginx self-signed override
您应该会进入您的站点。如果您查看浏览器地址栏,您会看到一个带有 “x” 的锁。在这种情况下,这只是意味着无法验证证书。它仍然在加密您的连接。
如果您配置了 Nginx 两个服务器块,自动将 HTTP 内容重定向到 HTTPS,您还可以检查重定向是否正常工作:
http://server_domain_or_IP
如果结果显示相同的图标,这意味着您的重定向已经正确工作。
步骤 6:更改为永久重定向
如果您的重定向工作正常,并且您确定只想允许加密流量,您应该修改 Nginx 配置以使重定向变为永久性。
再次打开您的服务器块配置文件:
sudo nano /etc/nginx/sites-available/default
找到 return 302
并将其更改为 return 301
:
server {listen 80 default_server;listen [::]:80 default_server;server_name server_domain_or_IP;return 301 https://$server_name$request_uri;
}. . .
保存并关闭文件。
检查您的配置是否存在语法错误:
sudo nginx -t
当您准备好时,重新启动 Nginx 以使重定向变为永久性:
sudo systemctl restart nginx
结论
您已经配置了 Nginx 服务器以使用强加密来处理客户端连接。这将允许您安全地提供请求,并防止外部方读取您的流量。
相关文章:
如何在 Ubuntu 16.04 上为 Nginx 创建自签名 SSL 证书
简介 TLS,即传输层安全协议,及其前身SSL,即安全套接字层,是用于将普通流量包装在受保护的加密包装中的网络协议。 使用这项技术,服务器可以在服务器和客户端之间安全地发送流量,而不会被外部方拦截。证书…...
5.协议的编解码
本章内容其实没有多大难度,主要考察大家的细心程度.计算数据长度然后截取相应字节数组并按照协议进行解码,编码则反之。 1.基础消息的编解码 Override public BasicMessage decode(byte[] bytes) {int dataLength ByteUtil.bytesToInt(ByteUtil.extra…...
数据结构基础| 线性表
线性表 定义 没有元素则为空表 例子: 稀疏多项式的运算 图书信息管理系统 特点 线性结构 同类型 线性表的类型定义 1.基本操作: InitList(&L) 操作结果:构造空的线性表L DestroyList(&L) 初始化条件:线性表L存在 操作结果:销毁线性表L(线性表L不存在) Cle…...

嵌入式学习
笔记 作业 有如下结构体 struct Student{ char name[16]; int age; double math_score; double chinese_score; double english_score; double physics_score; double chemistry…...

sass-loader和node-sass与node版本的依赖问题
sass-loader和node-sass与node版本的依赖问题 没有人会陪你走到最后,碰到了便是有缘,即使到了要下车的时候,也要心存感激地告别,在心里留下空白的一隅之地,多年后想起时依然心存甘味。——林清玄 报错截图 报错信息 np…...

基于BP神经网络的QPSK解调算法matlab性能仿真
目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ........................................................................ for ij 1:leng…...

Linux服务器常用巡检命令
在Linux服务器上进行常规巡检是确保服务器稳定性和安全性的重要措施之一。以下是一些常用的巡检命令和技巧: 1. 查看系统信息 1.1 系统信息显示 命令:uname -a [rootlinux100 ~]# uname -a Linux linux100 4.15.0-70-generic #79-Ubuntu SMP…...

VSCode 配置 CMake
VSCode 配置 C/C 环境的详细过程可参考:VSCode 配置 C/C 环境 1 配置C/C编译环境 如果是 Windows 环境,需要安装 MingW。 方案一 可以去官网(https://sourceforge.net/projects/mingw-w64/)下载安装包。 注意安装路径不要出现中文。 打开 windows she…...

《MATLAB科研绘图与学术图表绘制从入门到精通》示例:绘制德国每日风能和太阳能产量3D线图
在MATLAB中,要绘制3D线图,可以使用 plot3 函数。 在《MATLAB科研绘图与学术图表绘制从入门到精通》书中通过绘制德国每日风能和太阳能产量3D线图解释了如何在MATLAB中绘制3D线图。 购书地址:https://item.jd.com/14102657.html...
【信息系统项目管理师知识点速记】质量管理:控制质量
控制质量是为了评估绩效,确保项目输出完整、正确且满足客户期望,而监督和记录质量管理活动执行结果的过程。控制质量过程需要在整个项目期间开展,其目的是测量产品或服务的完整性、合规性和适用性,以确保项目达到主要干系人的质量要求。 12.5.1 输入 项目管理计划 质量管理…...

【云原生】Pod 的生命周期(一)
【云原生】Pod 的生命周期(一)【云原生】Pod 的生命周期(二) Pod 的生命周期(一) 1.Pod 生命期2.Pod 阶段3.容器状态3.1 Waiting (等待)3.2 Running(运行中)3…...

Golang | Leetcode Golang题解之第71题简化路径
题目: 题解: func simplifyPath(path string) string {stack : []string{}for _, name : range strings.Split(path, "/") {if name ".." {if len(stack) > 0 {stack stack[:len(stack)-1]}} else if name ! "" &am…...

Unreal游戏GPU性能优化检测模式全新上线
UWA已经在去年推出了针对于Unity项目的GPU性能优化工具,通过对GPU渲染性能、带宽性能以及各种下探指标,帮助Unity项目研发团队定位由GPU导致的发热耗电问题。这个需求在Unreal团队中也极为强烈,因此UWA将该功能移植到针对Unreal项目的GOT Onl…...

设计网页用什么软件
在设计网页时,可以使用多种软件来完成不同的任务。以下是一些常用的网页设计软件,以及它们的特点和用途。 1. Adobe Photoshop: Adobe Photoshop 是一款功能强大的图像编辑软件。在网页设计中,它常用于创建和编辑网页所需的图像、…...
⑪ - 测试工程师通识指南
📖 该文隶属 程序员:职场关键角色通识宝典✍️ 作者:哈哥撩编程(视频号同名) 博客专家全国博客之星第四名超级个体COC上海社区主理人特约讲师谷歌亚马逊演讲嘉宾科技博主极星会首批签约作者🏆 推荐专栏: 🏅 程序员:职场关键角色通识宝典🏅...

RabbitMQ知识点总结和复习
之前项目中用到RabbitMQ的场景主要是订单信息的传递,还有就是利用RabbitMQ的死信队列属性设置,实现延迟队列效果,实现超时支付取消功能,以及在两个不同项目中传递数据等场景。 最近几年的工作中都是一直用的RabbitMQ,…...

ContEA阅读笔记
Facing Changes: Continual Entity Alignment for Growing Knowledge Graphs 面对变化:不断增长的知识图谱的持续实体对齐 Abstract 实体对齐是知识图谱(KG)集成中一项基本且重要的技术。多年来,实体对齐的研究一直基于知识图谱是静态的假设ÿ…...

使用nvm切换nodejs版本
查看可以安装的版本: 使用nvm list显示已安装的nodejs版本: 选择一个版本下载: 切换对应的版本:...
机器学习_KNN算法
机器学习_KNN算法 K-近邻(K-Nearest Neighbors,简称KNN)算法是一种基本的机器学习分类和回归算法 其核心思想是:如果一个样本在特征空间中的k个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别…...

学QT的第一天~
#include "mywidget.h" MyWidget::MyWidget(QWidget *parent) : QWidget(parent) { //窗口相关设置// this->resize(427,330); this->setFixedSize(427,330); //设置图标 this->setWindowIcon(QIcon("C:\\Users\\Admin\\Desktop\\pictrue\\dahz.jpg&q…...

大数据学习栈记——Neo4j的安装与使用
本文介绍图数据库Neofj的安装与使用,操作系统:Ubuntu24.04,Neofj版本:2025.04.0。 Apt安装 Neofj可以进行官网安装:Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...

令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍
文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结: 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析: 实际业务去理解体会统一注…...
Python如何给视频添加音频和字幕
在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...

零基础设计模式——行为型模式 - 责任链模式
第四部分:行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习!行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想:使多个对象都有机会处…...

CMake 从 GitHub 下载第三方库并使用
有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...

从 GreenPlum 到镜舟数据库:杭银消费金融湖仓一体转型实践
作者:吴岐诗,杭银消费金融大数据应用开发工程师 本文整理自杭银消费金融大数据应用开发工程师在StarRocks Summit Asia 2024的分享 引言:融合数据湖与数仓的创新之路 在数字金融时代,数据已成为金融机构的核心竞争力。杭银消费金…...
Vue 模板语句的数据来源
🧩 Vue 模板语句的数据来源:全方位解析 Vue 模板(<template> 部分)中的表达式、指令绑定(如 v-bind, v-on)和插值({{ }})都在一个特定的作用域内求值。这个作用域由当前 组件…...

渗透实战PortSwigger靶场:lab13存储型DOM XSS详解
进来是需要留言的,先用做简单的 html 标签测试 发现面的</h1>不见了 数据包中找到了一个loadCommentsWithVulnerableEscapeHtml.js 他是把用户输入的<>进行 html 编码,输入的<>当成字符串处理回显到页面中,看来只是把用户输…...

rknn toolkit2搭建和推理
安装Miniconda Miniconda - Anaconda Miniconda 选择一个 新的 版本 ,不用和RKNN的python版本保持一致 使用 ./xxx.sh进行安装 下面配置一下载源 # 清华大学源(最常用) conda config --add channels https://mirrors.tuna.tsinghua.edu.cn…...